@brightcove/player-loader
Version:
An asynchronous script loader for the Brightcove Player.
340 lines (293 loc) • 9.13 kB
JavaScript
import {getDocument} from './utils/environment';
import urls from './urls';
import {
DEFAULT_ASPECT_RATIO,
DEFAULT_IFRAME_HORIZONTAL_PLAYLIST,
DEFAULT_MAX_WIDTH,
EMBED_TAG_NAME_VIDEOJS,
EMBED_TYPE_IFRAME,
REF_NODE_INSERT_PREPEND,
REF_NODE_INSERT_BEFORE,
REF_NODE_INSERT_AFTER,
REF_NODE_INSERT_REPLACE,
JSON_ALLOWED_ATTRS
} from './constants';
/**
* Is this value an element?
*
* @param {Element} el
* A maybe element.
*
* @return {boolean}
* Whether or not the value is a element.
*/
const isEl = (el) => Boolean(el && el.nodeType === 1);
/**
* Is this value an element with a parent node?
*
* @param {Element} el
* A maybe element.
*
* @return {boolean}
* Whether or not the value is a element with a parent node.
*/
const isElInDom = (el) => Boolean(isEl(el) && el.parentNode);
/**
* Creates an iframe embed code.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @return {Element}
* The DOM element that will ultimately be passed to the `bc()` function.
*/
const createIframeEmbed = (params) => {
const doc = getDocument();
const el = doc.createElement('iframe');
el.setAttribute('allow', 'autoplay;encrypted-media;fullscreen');
el.setAttribute('allowfullscreen', 'allowfullscreen');
el.src = urls.getUrl(params);
return el;
};
/**
* Creates an in-page embed code.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @return {Element}
* The DOM element that will ultimately be passed to the `bc()` function.
*/
const createInPageEmbed = (params) => {
const {embedOptions} = params;
const doc = getDocument();
// We DO NOT include the data-account, data-player, or data-embed attributes
// here because we will be manually initializing the player.
const paramsToAttrs = {
adConfigId: 'data-ad-config-id',
applicationId: 'data-application-id',
catalogSearch: 'data-catalog-search',
catalogSequence: 'data-catalog-sequence',
deliveryConfigId: 'data-delivery-config-id',
playlistId: 'data-playlist-id',
playlistVideoId: 'data-playlist-video-id',
poster: 'poster',
videoId: 'data-video-id'
};
const tagName = embedOptions && embedOptions.tagName || EMBED_TAG_NAME_VIDEOJS;
const el = doc.createElement(tagName);
Object.keys(paramsToAttrs)
.filter(key => params[key])
.forEach(key => {
let value;
// If it's not a string, such as with a catalog search or sequence, we
// try to encode it as JSON.
if (typeof params[key] !== 'string' && JSON_ALLOWED_ATTRS.indexOf(key) !== -1) {
try {
value = JSON.stringify(params[key]);
// If it fails, don't set anything.
} catch (x) {
return;
}
} else {
value = String(params[key]).trim();
}
el.setAttribute(paramsToAttrs[key], value);
});
el.setAttribute('controls', 'controls');
el.classList.add('video-js');
return el;
};
/**
* Wraps an element in responsive intrinsic ratio elements.
*
* @private
* @param {string} embedType
* The type of the embed.
*
* @param {Object} embedOptions
* Embed options from the params.
*
* @param {Element} el
* The DOM element.
*
* @return {Element}
* A new element (if needed).
*/
const wrapResponsive = (embedType, embedOptions, el) => {
if (!embedOptions.responsive) {
return el;
}
const doc = getDocument();
el.style.position = 'absolute';
el.style.top = '0px';
el.style.right = '0px';
el.style.bottom = '0px';
el.style.left = '0px';
el.style.width = '100%';
el.style.height = '100%';
const responsive = Object.assign({
aspectRatio: DEFAULT_ASPECT_RATIO,
iframeHorizontalPlaylist: DEFAULT_IFRAME_HORIZONTAL_PLAYLIST,
maxWidth: DEFAULT_MAX_WIDTH
}, embedOptions.responsive);
// This value is validate at a higher level, so we can trust that it's in the
// correct format.
const aspectRatio = responsive.aspectRatio.split(':').map(Number);
const inner = doc.createElement('div');
let paddingTop = (aspectRatio[1] / aspectRatio[0] * 100);
// For iframes with a horizontal playlist, the playlist takes up 20% of the
// vertical space (if shown); so, adjust the vertical size of the embed to
// avoid black bars.
if (embedType === EMBED_TYPE_IFRAME && responsive.iframeHorizontalPlaylist) {
paddingTop *= 1.25;
}
inner.style.paddingTop = paddingTop + '%';
inner.appendChild(el);
const outer = doc.createElement('div');
outer.style.position = 'relative';
outer.style.display = 'block';
outer.style.maxWidth = responsive.maxWidth;
outer.appendChild(inner);
return outer;
};
/**
* Wraps an element in a Picture-in-Picture plugin container.
*
* @private
* @param {Object} embedOptions
* Embed options from the params.
*
* @param {Element} el
* The DOM element.
*
* @return {Element}
* A new element (if needed).
*/
const wrapPip = (embedOptions, el) => {
if (!embedOptions.pip) {
return el;
}
const doc = getDocument();
const pip = doc.createElement('div');
pip.classList.add('vjs-pip-container');
pip.appendChild(el);
return pip;
};
/**
* Wraps a bare embed element with necessary parent elements, depending on
* embed options given in params.
*
* @private
* @param {string} embedType
* The type of the embed.
*
* @param {Object} embedOptions
* Embed options from the params.
*
* @param {Element} embed
* The embed DOM element.
*
* @return {Element}
* A new element (if needed) or the embed itself.
*/
const wrapEmbed = (embedType, embedOptions, embed) => {
if (!embedOptions) {
return embed;
}
return wrapPip(embedOptions, wrapResponsive(embedType, embedOptions, embed));
};
/**
* Inserts a previously-created embed element into the page based on params.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @param {Element} embed
* The embed DOM element.
*
* @return {Element}
* The embed DOM element.
*/
const insertEmbed = (params, embed) => {
const {refNode, refNodeInsert} = params;
const refNodeParent = refNode.parentNode;
// Wrap the embed, if needed, in container elements to support various
// plugins.
const wrapped = wrapEmbed(params.embedType, params.embedOptions, embed);
// Decide where to insert the wrapped embed.
if (refNodeInsert === REF_NODE_INSERT_BEFORE) {
refNodeParent.insertBefore(wrapped, refNode);
} else if (refNodeInsert === REF_NODE_INSERT_AFTER) {
refNodeParent.insertBefore(wrapped, refNode.nextElementSibling || null);
} else if (refNodeInsert === REF_NODE_INSERT_REPLACE) {
refNodeParent.replaceChild(wrapped, refNode);
} else if (refNodeInsert === REF_NODE_INSERT_PREPEND) {
refNode.insertBefore(wrapped, refNode.firstChild || null);
// Append is the default.
} else {
refNode.appendChild(wrapped);
}
// If the playlist embed option is provided, we need to add a playlist element
// immediately after the embed. This has to happen after the embed is inserted
// into the DOM (above).
if (params.embedOptions && params.embedOptions.playlist) {
const doc = getDocument();
const playlistTagName = params.embedOptions.playlist.legacy ? 'ul' : 'div';
const playlist = doc.createElement(playlistTagName);
playlist.classList.add('vjs-playlist');
embed.parentNode.insertBefore(playlist, embed.nextElementSibling || null);
}
// Clean up internal reference to the refNode to avoid potential memory
// leaks in case the params get persisted somewhere. We won't need it beyond
// this point.
params.refNode = null;
// Return the original embed element that can be passed to `bc()`.
return embed;
};
/**
* Handles `onEmbedCreated` callback invocation.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @param {Element} embed
* The embed DOM element.
*
* @return {Element}
* A possibly-new DOM element.
*/
const onEmbedCreated = (params, embed) => {
if (typeof params.onEmbedCreated !== 'function') {
return embed;
}
const result = params.onEmbedCreated(embed);
if (isEl(result)) {
return result;
}
return embed;
};
/**
* Creates an embed code of the appropriate type, runs any customizations
* necessary, and inserts it into the DOM.
*
* @param {Object} params
* A parameters object. See README for details.
*
* @return {Element}
* The DOM element that will ultimately be passed to the `bc()`
* function. Even when customized or wrapped, the return value will be
* the target element.
*/
const createEmbed = (params) => {
const embed = (params.embedType === EMBED_TYPE_IFRAME) ?
createIframeEmbed(params) :
createInPageEmbed(params);
return insertEmbed(params, onEmbedCreated(params, embed));
};
export default createEmbed;
export {isEl, isElInDom};