import EmbedPlayer from './EmbedPlayer';
import StickyVideo from './StickyVideo';
import Carousel from './Carousel';
/**
* Video carousel functionality.
* Initializes `GlideJS` carousel based on settings provided in constructor.
* Set's up all listeners and event handlers.
*
* @class VideoCarousel
* @prop {object} states - Set of classes that serve as flags for carousel states.
* @prop {object} selectors - Set of selectors that can be used to select carousel features.
* @prop {string} isStandalone - Setting identifies whether or not this is a standalone carousel.
* @prop {string} currentVideoId - ID of the current active video iframe.
* @prop {element} $elem - The video carousel element itself.
* @prop {element} $player - The current active video player object.
* @prop {element} $label - Featured video label element if one exists (standalone carousels only)
* @prop {element} $title - Featured video title element if one exists (standalone carousels only)
* @prop {element} $description - Featured video description element if one exists
* (standalone carousels only)
* @prop {element} $link - Featured video link element if one exists (standalone carousels only)
*/
class VideoCarousel {
static states = {
playing: 'c-posts__item--playing',
};
static selectors = {
carousel: '.l-videoCarousel',
video: '.l-videoCarousel__featured .c-video',
embed: '.l-videoCarousel__featured .c-video__embed',
thumbnail: '.l-videoCarousel__link:not(.l-videoCarousel__link--page)',
slide: '.c-posts__item',
activeSlide: '.glide__slide--active',
label: '.l-videoCarousel__info',
title: '.l-videoCarousel__fullTitle',
description: '.l-videoCarousel__description',
link: '.l-videoCarousel__details',
edit: '.c-editButton__link',
};
static instances = {};
isStandalone = false;
currentVideoId = null;
carousel = null;
$elem = null;
$player = null;
$label = null;
$title = null;
$description = null;
$link = null;
/**
* @constructs
* @param {object} $elem - The video carousel element object.
* @param {object} settings - Carousel settings to be merged with defaults
*/
constructor( $elem, settings ) {
this.$elem = $elem;
this.isStandalone = settings.isStandalone;
// create carousel object
const carouselOptions = Object.assign({}, settings.carousel, { layoutClass: 'l-videoCarousel' });
this.carousel = new Carousel( $elem, carouselOptions );
}
/**
* Initialize video carousel, set up class members, and bind event listeners.
*
* @method init
*/
init() {
// initialize carousel
this.carousel.init();
this.$player = this.$elem.querySelector( VideoCarousel.selectors.video );
this.$slides = this.$elem.querySelectorAll( VideoCarousel.selectors.slide );
// Bail if there are no videos in video section
if ( ! this.$player ) {
return;
}
this.currentVideoId = this.$player.dataset.displayinlinePlayerId;
this.$elem.dataset.videoCarouselInit = 'true';
// initialize red label, title, description, and link elements if it's a standalone carousel
if ( this.isStandalone ) {
this.$label = this.$elem.querySelector( VideoCarousel.selectors.label );
this.$title = this.$elem.querySelector( VideoCarousel.selectors.title );
this.$description = this.$elem.querySelector( VideoCarousel.selectors.description );
this.$link = this.$elem.querySelector( VideoCarousel.selectors.link );
// add setting to restrict sticky videos to mobile.
// standalone player need not be sticky on desktop.
if ( this.enableAtWidth ) {
this.$player.dataset.displayinlineStickyOnMobile = this.enableAtWidth;
}
}
// Only bind videos if slides trigger inline playback.
if ( this.$elem.dataset.videoCarouselPlayInline ) {
this.bindVideos();
}
const id = this.$elem.getAttribute( 'id' );
if ( id ) {
VideoCarousel.instances[id] = this;
}
window.addEventListener( 'message', VideoCarousel.receiveMessage );
}
/**
* Bind video slide click listeners. Clicking on a video slide should load video into player.
*
* @method bindVideos
*/
bindVideos() {
const $videos = this.$elem.querySelectorAll( VideoCarousel.selectors.thumbnail );
[].forEach.call( $videos, ( $video ) => {
$video.addEventListener( 'click', event => this.handleVideoClick( event, $video ) );
});
}
/**
* Video slide click handler. Loads selected video into player if it is not there already.
* Updates now-playing flag on carousel to highlight selected video.
*
* @method handleVideoClick
* @param {object} event - Video slide click event instance.
* @param {object} $video - Video slide element object.
*/
handleVideoClick( event, $video ) {
event.preventDefault();
if ( ! $video.classList.contains( VideoCarousel.states.playing ) ) {
const { videoPlayerId } = $video.dataset;
const { videoCarouselUrl } = $video.dataset;
const { videoCarouselPlaylist } = $video.dataset;
this.swapVideo( videoPlayerId, videoCarouselUrl, videoCarouselPlaylist, $video );
VideoCarousel.updateNowPlaying( $video.dataset.videoId, this.$elem );
this.currentVideoId = videoPlayerId;
}
}
/**
* Swap out current carousel video with a new video and restart player.
*
* @method swapVideo
* @param {object} playerId - ID of current active video iframe.
* @param {url} embedUrl - New video player (and playlist) url.
* @param {string} playlist - Comma separated list of successive video ids in playlist.
* @param {object} $video - Video slide element object.
*/
swapVideo( playerId, embedUrl, playlist, $video ) {
const $stickyVideoElem = document.querySelector( `#${this.currentVideoId}__sticky` );
if ( $stickyVideoElem ) {
StickyVideo.detachCurrentVideo();
$stickyVideoElem.parentNode.removeChild( $stickyVideoElem );
} else {
const $videoElem = this.$player.querySelector( VideoCarousel.selectors.embed );
if ( $videoElem ) {
$videoElem.parentNode.removeChild( $videoElem );
}
}
this.$player.dataset.displayinlineInit = '';
this.$player.dataset.displayinline = embedUrl;
this.$player.dataset.displayinlinePlayerId = playerId;
this.$player.dataset.displayinlinePlaylist = playlist;
this.$player.dataset.displayinlineVideoId = $video.dataset.videoId;
// update red label, title, description, and link if this is a standalone carousel
if ( this.isStandalone ) {
VideoCarousel.updateDetails( this, $video );
}
// Swap edit video link it present (e.g. for logged in users)
const $editButton = this.$player.querySelector( VideoCarousel.selectors.edit );
if ( $editButton ) {
const oldLink = $editButton.getAttribute( 'href' );
const newLink = oldLink.replace( /post=[\d]+/, `post=${$video.dataset.videoId}` );
$editButton.setAttribute( 'href', newLink );
}
const $embedPlayer = new EmbedPlayer( this.$player );
$embedPlayer.start();
}
/**
* Update video details
*
* @static
* @method updateDetails
* @param {object} carousel - VideoCarousel
* @param {object} $video - $video element
*/
static updateDetails( carousel, $video ) {
/* eslint-disable no-param-reassign */
carousel.$label.innerText = $video.dataset.videoCarouselLabel;
carousel.$title.innerText = $video.getAttribute( 'title' );
carousel.$description.innerText = $video.dataset.videoCarouselContent;
carousel.$link.setAttribute( 'href', $video.getAttribute( 'href' ) );
/* eslint-enable no-param-reassign */
}
/**
* Checks to see if play event has fired for current active carousel video.
* If so, grabs current playlist index and passes along to update now playing flag.
* This method has to be static as we can't bind `postMessage` events to object instances.
*
* @static
* @method receiveMessage
* @param {object} data - postMessage data object.
*/
static receiveMessage( data ) {
if ( data.status && data.iframeId && 'undefined' !== typeof data.playlistIndex ) {
if ( 'playlistItem' === data.status ) {
// videoPostId will not be defined for live stream
// live stream would only be selectable from the carousel, not from video playlist
// Bail if the video is played within the expanded iframe, rather than generic main player.
if ( data.iframeId.indexOf( data.videoPostId ) > 0 ) {
return;
}
const $video = document.querySelector( `[data-video-id="${data.videoPostId}"]` );
if ( $video ) {
const carouselId = VideoCarousel.getId( $video.dataset.videoPlayerId );
const carousel = VideoCarousel.instances[carouselId];
if ( carousel.isStandalone ) {
VideoCarousel.updateDetails( carousel, $video );
}
VideoCarousel.updateNowPlaying( $video.dataset.videoId, carousel.$elem );
}
}
}
}
/**
* Updates the now playing flag based on the current active `videoId`.
*
* @static
* @method updateNowPlayling
* @param {string} iframeId - ID of current active video iframe.
* @param {object} $carousel - Video carousel element object.
*/
static updateNowPlaying( videoId, $carousel ) {
const $oldPlaying = $carousel.querySelectorAll( `.${VideoCarousel.states.playing}` );
[].forEach.call( $oldPlaying, ( $slide ) => {
$slide.classList.remove( VideoCarousel.states.playing );
});
const $playing = $carousel.querySelector( `[data-video-id="${videoId}"]` );
$playing.parentNode.classList.add( VideoCarousel.states.playing );
Carousel.goToSlide( $playing, $carousel );
}
/**
* Get video carousel id
*
* @static
* @method getId
* @param {sting} playerId
* @return {string} The corresponding ID for that video carousel.
*/
static getId( playerId ) {
return `${playerId}-carousel`;
}
}
export default VideoCarousel;
// @TODO: liveblog video carousel