@brightcove/react-player-loader
Version:
The official react component for the Brightcove Player
1,603 lines (1,359 loc) • 45.2 kB
JavaScript
/*! @name @brightcove/react-player-loader @version 1.4.2 @license Apache-2.0 */
import React from 'react';
import document from 'global/document';
import window from 'global/window';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
/*! @name @brightcove/player-loader @version 1.8.0 @license Apache-2.0 */
function _extends$1() {
_extends$1 = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends$1.apply(this, arguments);
}
var version = "1.8.0";
/*! @name @brightcove/player-url @version 1.2.0 @license Apache-2.0 */
var version$1 = "1.2.0";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
// The parameters that may include JSON.
var JSON_ALLOWED_PARAMS = ['catalogSearch', 'catalogSequence'];
// The parameters that may be set as query string parameters for iframes.
var IFRAME_ALLOWED_QUERY_PARAMS = ['adConfigId', 'applicationId', 'catalogSearch', 'catalogSequence', 'playlistId', 'playlistVideoId', 'videoId'];
/**
* Gets the value of a parameter and encodes it as a string.
*
* For certain keys, JSON is allowed and will be encoded.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @param {string} key
* The key in the params object.
*
* @return {string|undefined}
* The encoded value - or `undefined` if none.
*/
var getQueryParamValue = function getQueryParamValue(params, key) {
if (!params || params[key] === undefined) {
return;
}
// 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_PARAMS.indexOf(key) !== -1) {
try {
return encodeURIComponent(JSON.stringify(params[key]));
} catch (x) {
// If it's not a string and we can't encode as JSON, it's ignored entirely.
return;
}
}
return encodeURIComponent(String(params[key]).trim()) || undefined;
};
/**
* In some cases, we need to add query string parameters to an iframe URL.
*
* @private
* @param {Object} params
* An object of query parameters.
*
* @return {string}
* A query string starting with `?`. If no valid parameters are given,
* returns an empty string.
*/
var getQueryString = function getQueryString(params) {
return Object.keys(params).filter(function (k) {
return IFRAME_ALLOWED_QUERY_PARAMS.indexOf(k) !== -1;
}).reduce(function (qs, k) {
var value = getQueryParamValue(params, k);
if (value !== undefined) {
qs += qs ? '&' : '?';
qs += encodeURIComponent(k) + '=' + value;
}
return qs;
}, '');
};
/**
* Generate a URL to a Brightcove Player.
*
* @param {Object} params
* A set of parameters describing the player URL to create.
*
* @param {string} params.accountId
* A Brightcove account ID.
*
* @param {string} [params.playerId="default"]
* A Brightcove player ID.
*
* @param {string} [params.embedId="default"]
* A Brightcove player embed ID.
*
* @param {boolean} [params.iframe=false]
* Whether to return a URL for an HTML document to be embedded in
* an iframe.
*
* @param {boolean} [params.minified=true]
* When the `iframe` argument is `false`, this can be used to control
* whether the minified or unminified JavaScript URL is returned.
*
* @param {string} [params.base="https://players.brightcove.net"]
* A base CDN protocol and hostname. Mainly used for testing.
*
* @return {string}
* A URL to a Brightcove Player.
*/
var brightcovePlayerUrl = function brightcovePlayerUrl(_ref) {
var accountId = _ref.accountId,
_ref$base = _ref.base,
base = _ref$base === undefined ? 'https://players.brightcove.net' : _ref$base,
_ref$playerId = _ref.playerId,
playerId = _ref$playerId === undefined ? 'default' : _ref$playerId,
_ref$embedId = _ref.embedId,
embedId = _ref$embedId === undefined ? 'default' : _ref$embedId,
_ref$iframe = _ref.iframe,
iframe = _ref$iframe === undefined ? false : _ref$iframe,
_ref$minified = _ref.minified,
minified = _ref$minified === undefined ? true : _ref$minified,
_ref$queryParams = _ref.queryParams,
queryParams = _ref$queryParams === undefined ? null : _ref$queryParams;
var ext = '';
if (iframe) {
ext += 'html';
} else {
if (minified) {
ext += 'min.';
}
ext += 'js';
}
if (base.charAt(base.length - 1) === '/') {
base = base.substring(0, base.length - 1);
}
var qs = '';
if (iframe && queryParams && (typeof queryParams === 'undefined' ? 'undefined' : _typeof(queryParams)) === 'object') {
qs = getQueryString(queryParams);
}
accountId = encodeURIComponent(accountId);
playerId = encodeURIComponent(playerId);
embedId = encodeURIComponent(embedId);
return base + '/' + accountId + '/' + playerId + '_' + embedId + '/index.' + ext + qs;
};
/**
* The version of this module.
*
* @type {string}
*/
brightcovePlayerUrl.VERSION = version$1;
var DEFAULTS = {
embedId: 'default',
embedType: 'in-page',
playerId: 'default',
Promise: window.Promise,
refNodeInsert: 'append'
};
var DEFAULT_ASPECT_RATIO = '16:9';
var DEFAULT_IFRAME_HORIZONTAL_PLAYLIST = false;
var DEFAULT_MAX_WIDTH = '100%';
var EMBED_TAG_NAME_VIDEO = 'video';
var EMBED_TAG_NAME_VIDEOJS = 'video-js';
var EMBED_TYPE_IN_PAGE = 'in-page';
var EMBED_TYPE_IFRAME = 'iframe';
var REF_NODE_INSERT_APPEND = 'append';
var REF_NODE_INSERT_PREPEND = 'prepend';
var REF_NODE_INSERT_BEFORE = 'before';
var REF_NODE_INSERT_AFTER = 'after';
var REF_NODE_INSERT_REPLACE = 'replace';
var JSON_ALLOWED_ATTRS = ['catalogSearch', 'catalogSequence'];
var BASE_URL = 'https://players.brightcove.net/';
/**
* Gets the URL to a player on CDN.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @return {string}
* A URL.
*/
var getUrl = function getUrl(params) {
if (params.playerUrl) {
return params.playerUrl;
}
var accountId = params.accountId,
playerId = params.playerId,
embedId = params.embedId,
embedOptions = params.embedOptions;
var iframe = params.embedType === EMBED_TYPE_IFRAME;
return brightcovePlayerUrl({
accountId: accountId,
playerId: playerId,
embedId: embedId,
iframe: iframe,
base: BASE_URL,
// The unminified embed option is the exact reverse of the minified option
// here.
minified: embedOptions ? !embedOptions.unminified : true,
// Pass the entire params object as query params. This is safe because
// @brightcove/player-url only accepts a whitelist of parameters. Anything
// else will be ignored.
queryParams: params
});
};
/**
* Function used to get the base URL - primarily for testing.
*
* @private
* @return {string}
* The current base URL.
*/
var getBaseUrl = function getBaseUrl() {
return BASE_URL;
};
/**
* Function used to set the base URL - primarily for testing.
*
* @private
* @param {string} baseUrl
* A new base URL (instead of Brightcove CDN).
*/
var setBaseUrl = function setBaseUrl(baseUrl) {
BASE_URL = baseUrl;
};
var urls = {
getUrl: getUrl,
getBaseUrl: getBaseUrl,
setBaseUrl: setBaseUrl
};
/**
* Is this value an element?
*
* @param {Element} el
* A maybe element.
*
* @return {boolean}
* Whether or not the value is a element.
*/
var isEl = function isEl(el) {
return 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.
*/
var isElInDom = function isElInDom(el) {
return 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.
*/
var createIframeEmbed = function createIframeEmbed(params) {
var el = document.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.
*/
var createInPageEmbed = function createInPageEmbed(params) {
var embedOptions = params.embedOptions; // We DO NOT include the data-account, data-player, or data-embed attributes
// here because we will be manually initializing the player.
var 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'
};
var tagName = embedOptions && embedOptions.tagName || EMBED_TAG_NAME_VIDEOJS;
var el = document.createElement(tagName);
Object.keys(paramsToAttrs).filter(function (key) {
return params[key];
}).forEach(function (key) {
var 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).
*/
var wrapResponsive = function wrapResponsive(embedType, embedOptions, el) {
if (!embedOptions.responsive) {
return el;
}
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%';
var responsive = _extends$1({
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.
var aspectRatio = responsive.aspectRatio.split(':').map(Number);
var inner = document.createElement('div');
var 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);
var outer = document.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).
*/
var wrapPip = function wrapPip(embedOptions, el) {
if (!embedOptions.pip) {
return el;
}
var pip = document.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.
*/
var wrapEmbed = function 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.
*/
var insertEmbed = function insertEmbed(params, embed) {
var refNode = params.refNode,
refNodeInsert = params.refNodeInsert;
var refNodeParent = refNode.parentNode; // Wrap the embed, if needed, in container elements to support various
// plugins.
var 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) {
var playlistTagName = params.embedOptions.playlist.legacy ? 'ul' : 'div';
var playlist = document.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.
*/
var onEmbedCreated = function onEmbedCreated(params, embed) {
if (typeof params.onEmbedCreated !== 'function') {
return embed;
}
var 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.
*/
var createEmbed = function createEmbed(params) {
var embed = params.embedType === EMBED_TYPE_IFRAME ? createIframeEmbed(params) : createInPageEmbed(params);
return insertEmbed(params, onEmbedCreated(params, embed));
};
//
// The keys follow the format "accountId_playerId_embedId" where accountId is
// optional and defaults to "*". This happens when we detect pre-existing
// player globals.
var actualCache = new window.Map();
/**
* Get the cache key given some properties.
*
* @private
* @param {Object} props
* Properties describing the player record to cache.
*
* @param {string} props.playerId
* A player ID.
*
* @param {string} props.embedId
* An embed ID.
*
* @param {string} [props.accountId="*"]
* An optional account ID. This is optional because when we search for
* pre-existing players to avoid downloads, we will not necessarily
* know the account ID.
*
* @return {string}
* A key to be used in the script cache.
*/
var key = function key(_ref) {
var accountId = _ref.accountId,
playerId = _ref.playerId,
embedId = _ref.embedId;
return (accountId || '*') + "_" + playerId + "_" + embedId;
};
/**
* Add an entry to the script cache.
*
* @private
* @param {Object} props
* Properties describing the player record to cache.
*
* @param {string} props.playerId
* A player ID.
*
* @param {string} props.embedId
* An embed ID.
*
* @param {string} [props.accountId="*"]
* An optional account ID. This is optional because when we search for
* pre-existing players to avoid downloads, we will not necessarily
* know the account ID. If not given, we assume that no script was
* downloaded for this player.
*/
var store = function store(props) {
actualCache.set(key(props), props.accountId ? urls.getUrl(props) : '');
};
/**
* Checks if the script cache has an entry.
*
* @private
* @param {Object} props
* Properties describing the player record to cache.
*
* @param {string} props.playerId
* A player ID.
*
* @param {string} props.embedId
* An embed ID.
*
* @param {string} [props.accountId="*"]
* An optional account ID. This is optional because when we search for
* pre-existing players to avoid downloads, we will not necessarily
* know the account ID.
*
* @return {boolean}
* Will be `true` if there is a matching cache entry.
*/
var has = function has(props) {
return actualCache.has(key(props));
};
/**
* Gets a cache entry.
*
* @private
* @param {Object} props
* Properties describing the player record to cache.
*
* @param {string} props.playerId
* A player ID.
*
* @param {string} props.embedId
* An embed ID.
*
* @param {string} [props.accountId="*"]
* An optional account ID. This is optional because when we search for
* pre-existing players to avoid downloads, we will not necessarily
* know the account ID.
*
* @return {string}
* A cache entry - a URL or empty string.
*
*/
var get = function get(props) {
return actualCache.get(key(props));
};
/**
* Clears the cache.
*/
var clear = function clear() {
actualCache.clear();
};
/**
* Iterates over the cache.
*
* @param {Function} fn
* A callback function that will be called with a value and a key
* for each item in the cache.
*/
var forEach = function forEach(fn) {
actualCache.forEach(fn);
};
var playerScriptCache = {
clear: clear,
forEach: forEach,
get: get,
has: has,
key: key,
store: store
};
var REGEX_PLAYER_EMBED = /^([A-Za-z0-9]+)_([A-Za-z0-9]+)$/;
/**
* Gets an array of current per-player/per-embed `bc` globals that are
* attached to the `bc` global (e.g. `bc.abc123xyz_default`).
*
* If `bc` is not defined, returns an empty array.
*
* @private
* @return {string[]}
* An array of keys.
*/
var getBcGlobalKeys = function getBcGlobalKeys() {
return window.bc ? Object.keys(window.bc).filter(function (k) {
return REGEX_PLAYER_EMBED.test(k);
}) : [];
};
/**
* Gets known global object keys that Brightcove Players may create.
*
* @private
* @return {string[]}
* An array of global variables that were added during testing.
*/
var getGlobalKeys = function getGlobalKeys() {
return Object.keys(window).filter(function (k) {
return /^videojs/i.test(k) || /^(bc)$/.test(k);
});
};
/**
* Dispose all players from a copy of Video.js.
*
* @param {Function} videojs
* A copy of Video.js.
*/
var disposeAll = function disposeAll(videojs) {
if (!videojs) {
return;
}
Object.keys(videojs.players).forEach(function (k) {
var p = videojs.players[k];
if (p) {
p.dispose();
}
});
};
/**
* Resets environment state.
*
* This will dispose ALL Video.js players on the page and remove ALL `bc` and
* `videojs` globals it finds.
*/
var reset = function reset() {
// Remove all script elements from the DOM.
playerScriptCache.forEach(function (value, key) {
// If no script URL is associated, skip it.
if (!value) {
return;
} // Find all script elements and remove them.
Array.prototype.slice.call(document.querySelectorAll("script[src=\"" + value + "\"]")).forEach(function (el) {
return el.parentNode.removeChild(el);
});
}); // Clear the internal cache that have been downloaded.
playerScriptCache.clear(); // Dispose any remaining players from the `videojs` global.
disposeAll(window.videojs); // There may be other `videojs` instances lurking in the bowels of the
// `bc` global. This should eliminate any of those.
getBcGlobalKeys().forEach(function (k) {
return disposeAll(window.bc[k].videojs);
}); // Delete any global object keys that were created.
getGlobalKeys().forEach(function (k) {
delete window[k];
});
};
/**
* At runtime, populate the cache with pre-detected players. This allows
* people who have bundled their player or included a script tag before this
* runs to not have to re-download players.
*/
var detectPlayers = function detectPlayers() {
getBcGlobalKeys().forEach(function (k) {
var matches = k.match(REGEX_PLAYER_EMBED);
var props = {
playerId: matches[1],
embedId: matches[2]
};
if (!playerScriptCache.has(props)) {
playerScriptCache.store(props);
}
});
};
var env = {
detectPlayers: detectPlayers,
reset: reset
};
env.detectPlayers();
/**
* Is this value a function?
*
* @private
* @param {Function} fn
* A maybe function.
*
* @return {boolean}
* Whether or not the value is a function.
*/
var isFn = function isFn(fn) {
return typeof fn === 'function';
};
/**
* Checks whether an embedType parameter is valid.
*
* @private
* @param {string} embedType
* The value to test.
*
* @return {boolean}
* Whether the value is valid.
*/
var isValidEmbedType = function isValidEmbedType(embedType) {
return embedType === EMBED_TYPE_IN_PAGE || embedType === EMBED_TYPE_IFRAME;
};
/**
* Checks whether an embedOptions.tagName parameter is valid.
*
* @private
* @param {string} tagName
* The value to test.
*
* @return {boolean}
* Whether the value is valid.
*/
var isValidTagName = function isValidTagName(tagName) {
return tagName === EMBED_TAG_NAME_VIDEOJS || tagName === EMBED_TAG_NAME_VIDEO;
};
/**
* Checks whether a refNodeInsert parameter is valid.
*
* @private
* @param {string} refNodeInsert
* The value to test.
*
* @return {boolean}
* Whether the value is valid.
*/
var isValidRootInsert = function isValidRootInsert(refNodeInsert) {
return refNodeInsert === REF_NODE_INSERT_APPEND || refNodeInsert === REF_NODE_INSERT_PREPEND || refNodeInsert === REF_NODE_INSERT_BEFORE || refNodeInsert === REF_NODE_INSERT_AFTER || refNodeInsert === REF_NODE_INSERT_REPLACE;
};
/**
* Checks parameters and throws an error on validation problems.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @throws {Error} If accountId is missing.
* @throws {Error} If refNode is missing or invalid.
* @throws {Error} If embedType is missing or invalid.
* @throws {Error} If attempting to use an iframe embed with options.
* @throws {Error} If attempting to use embedOptions.responsiveIframe with a
* non-iframe embed.
* @throws {Error} If refNodeInsert is missing or invalid.
*/
var checkParams = function checkParams(params) {
var accountId = params.accountId,
embedOptions = params.embedOptions,
embedType = params.embedType,
options = params.options,
refNode = params.refNode,
refNodeInsert = params.refNodeInsert;
if (!accountId) {
throw new Error('accountId is required');
} else if (!isElInDom(refNode)) {
throw new Error('refNode must resolve to a node attached to the DOM');
} else if (!isValidEmbedType(embedType)) {
throw new Error('embedType is missing or invalid');
} else if (embedType === EMBED_TYPE_IFRAME && options) {
throw new Error('cannot use options with an iframe embed');
} else if (embedOptions && embedOptions.tagName !== undefined && !isValidTagName(embedOptions.tagName)) {
throw new Error("embedOptions.tagName is invalid (value: \"" + embedOptions.tagName + "\")");
} else if (embedOptions && embedOptions.responsive && embedOptions.responsive.aspectRatio && !/^\d+\:\d+$/.test(embedOptions.responsive.aspectRatio)) {
throw new Error("embedOptions.responsive.aspectRatio must be in the \"n:n\" format (value: \"" + embedOptions.responsive.aspectRatio + "\")");
} else if (!isValidRootInsert(refNodeInsert)) {
throw new Error('refNodeInsert is missing or invalid');
}
};
/**
* Normalizes a `refNode` param to an element - or `null`.
*
* @private
* @param {Element|string} refNode
* The value of a `refNode` param.
*
* @return {Element|null}
* A DOM element or `null` if the `refNode` was given as a string and
* did not match an element.
*/
var resolveRefNode = function resolveRefNode(refNode) {
if (isElInDom(refNode)) {
return refNode;
}
if (typeof refNode === 'string') {
return document.querySelector(refNode);
}
return null;
};
/**
* Initializes a player and returns it.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @param {Element} embed
* An element that will be passed to the `bc()` function.
*
* @param {Function} resolve
* A function to call if a player is successfully initialized.
*
* @param {Function} reject
* A function to call if a player fails to be initialized.
*
* @return {Object}
* A success object whose `ref` is a player.
*/
var initPlayer = function initPlayer(params, embed, resolve, reject) {
var embedId = params.embedId,
playerId = params.playerId;
var bc = window.bc[playerId + "_" + embedId] || window.bc;
if (!bc) {
return reject(new Error("missing bc function for " + playerId));
}
playerScriptCache.store(params);
var player;
try {
player = bc(embed, params.options); // Add a PLAYER_LOADER property to bcinfo to indicate this player was
// loaded via that mechanism.
if (player.bcinfo) {
player.bcinfo.PLAYER_LOADER = true;
}
} catch (x) {
var message = 'Could not initialize the Brightcove Player.'; // Update the rejection message based on known conditions that can cause it.
if (params.embedOptions.tagName === EMBED_TAG_NAME_VIDEOJS) {
message += ' You are attempting to embed using a "video-js" element.' + ' Please ensure that your Player is v6.11.0 or newer in order to' + ' support this embed type. Alternatively, pass `"video"` for' + ' `embedOptions.tagName`.';
}
return reject(new Error(message));
}
resolve({
type: EMBED_TYPE_IN_PAGE,
ref: player
});
};
/**
* Loads a player from CDN and embeds it.
*
* @private
* @param {Object} params
* A parameters object. See README for details.
*
* @param {Function} resolve
* A function to call if a player is successfully initialized.
*
* @param {Function} reject
* A function to call if a player fails to be initialized.
*/
var loadPlayer = function loadPlayer(params, resolve, reject) {
params.refNode = resolveRefNode(params.refNode);
checkParams(params);
var refNode = params.refNode,
refNodeInsert = params.refNodeInsert; // Store a reference to the refNode parent. When we use the replace method,
// we'll need it as the location to store the script element.
var refNodeParent = refNode.parentNode;
var embed = createEmbed(params); // If this is an iframe, all we need to do is create the embed code and
// inject it. Because there is no reliable way to hook into an iframe from
// the parent page, we simply resolve immediately upon creating the embed.
if (params.embedType === EMBED_TYPE_IFRAME) {
resolve({
type: EMBED_TYPE_IFRAME,
ref: embed
});
return;
} // If we've already downloaded this script or detected a matching global, we
// should have the proper `bc` global and can bypass the script creation
// process.
if (playerScriptCache.has(params)) {
return initPlayer(params, embed, resolve, reject);
}
var script = document.createElement('script');
script.onload = function () {
return initPlayer(params, embed, resolve, reject);
};
script.onerror = function () {
reject(new Error('player script could not be downloaded'));
};
script.async = true;
script.charset = 'utf-8';
script.src = urls.getUrl(params);
if (refNodeInsert === REF_NODE_INSERT_REPLACE) {
refNodeParent.appendChild(script);
} else {
refNode.appendChild(script);
}
};
/**
* A function for asynchronously loading a Brightcove Player into a web page.
*
* @param {Object} parameters
* A parameters object. See README for details.
*
* @return {Promise|undefined}
* A Promise, if possible.
*/
var brightcovePlayerLoader = function brightcovePlayerLoader(parameters) {
var params = _extends$1({}, DEFAULTS, parameters);
var Promise = params.Promise,
onSuccess = params.onSuccess,
onFailure = params.onFailure; // When Promise is not available or any success/failure callback is given,
// do not attempt to use Promises.
if (!isFn(Promise) || isFn(onSuccess) || isFn(onFailure)) {
return loadPlayer(params, isFn(onSuccess) ? onSuccess : function () {}, isFn(onFailure) ? onFailure : function (err) {
throw err;
});
} // Promises are supported, use 'em.
return new Promise(function (resolve, reject) {
return loadPlayer(params, resolve, reject);
});
};
/**
* Expose a non-writable, non-configurable property on the
* `brightcovePlayerLoader` function.
*
* @private
* @param {string} key
* The property key.
*
* @param {string|Function} value
* The value.
*/
var expose = function expose(key, value) {
Object.defineProperty(brightcovePlayerLoader, key, {
configurable: false,
enumerable: true,
value: value,
writable: false
});
};
/**
* Get the base URL for players. By default, this will be the Brightcove CDN.
*
* @return {string}
* The current base URL.
*/
expose('getBaseUrl', function () {
return urls.getBaseUrl();
});
/**
* Set the base URL for players. By default, this will be the Brightcove CDN,
* but can be overridden with this function.
*
* @param {string} baseUrl
* A new base URL (instead of Brightcove CDN).
*/
expose('setBaseUrl', function (baseUrl) {
urls.setBaseUrl(baseUrl);
});
/**
* Get the URL for a player.
*/
expose('getUrl', function (options) {
return urls.getUrl(options);
});
/**
* Completely resets global state.
*
* This will dispose ALL Video.js players on the page and remove ALL `bc` and
* `videojs` globals it finds.
*/
expose('reset', function () {
return env.reset();
}); // Define some read-only constants on the exported function.
[['EMBED_TAG_NAME_VIDEO', EMBED_TAG_NAME_VIDEO], ['EMBED_TAG_NAME_VIDEOJS', EMBED_TAG_NAME_VIDEOJS], ['EMBED_TYPE_IN_PAGE', EMBED_TYPE_IN_PAGE], ['EMBED_TYPE_IFRAME', EMBED_TYPE_IFRAME], ['REF_NODE_INSERT_APPEND', REF_NODE_INSERT_APPEND], ['REF_NODE_INSERT_PREPEND', REF_NODE_INSERT_PREPEND], ['REF_NODE_INSERT_BEFORE', REF_NODE_INSERT_BEFORE], ['REF_NODE_INSERT_AFTER', REF_NODE_INSERT_AFTER], ['REF_NODE_INSERT_REPLACE', REF_NODE_INSERT_REPLACE], ['VERSION', version]].forEach(function (arr) {
expose(arr[0], arr[1]);
});
/**
* These prop changes can be handled by an internal player state change rather
* than a full dispose/recreate.
*
* @private
* @type {Object}
*/
var UPDATEABLE_PROPS = ['catalogSearch', 'catalogSequence', 'playlistId', 'playlistVideoId', 'videoId'];
var logError = function logError(err) {
/* eslint-disable no-console */
if (err && console && console.error) {
console.error(err);
}
/* eslint-enable no-console */
};
/**
* The official React component for the Brightcove Player!
*
* This uses `@brightcove/player-loader` to load a player into a React
* component based on the given props.
*/
var ReactPlayerLoader =
/*#__PURE__*/
function (_React$Component) {
_inheritsLoose(ReactPlayerLoader, _React$Component);
/**
* Create a new Brightcove player.
*
* @param {Object} props
* Most options will be passed along to player-loader, except for
* options that are listed. See README.md for more detail.
*
* @param {string} [props.baseUrl]
* The base URL to use when requesting a player
*
* @param {Object} [props.attrs]
* Used to set attributes on the component element that contains the
* embedded Brightcove Player.
*
* @param {boolean} [props.manualReloadFromPropChanges]
* Used to specify if reloading the player after prop changes will be handled manually.
*
*/
function ReactPlayerLoader(props) {
var _this;
_this = _React$Component.call(this, props) || this;
_this.refNode = null;
_this.setRefNode = function (ref) {
_this.refNode = ref;
};
_this.loadPlayer = _this.loadPlayer.bind(_assertThisInitialized(_this));
return _this;
}
/**
* Loads a new player based on the current props.
*/
var _proto = ReactPlayerLoader.prototype;
_proto.loadPlayer = function loadPlayer() {
var _this2 = this;
// If there is any player currently loaded, dispose it before fetching a
// new one.
this.disposePlayer(); // We need to provide our own callbacks below, so we cache these
// user-provided callbacks for use later.
var userSuccess = this.props.onSuccess;
var userFailure = this.props.onFailure;
var options = _extends({}, this.props, {
refNode: this.refNode,
refNodeInsert: 'append',
onSuccess: function onSuccess(_ref) {
var ref = _ref.ref,
type = _ref.type;
// If the component is not mounted when the callback fires, dispose
// the player and bail out.
if (!_this2.isMounted_) {
_this2.disposePlayer(ref);
return;
} // Store a player reference on the component.
_this2.player = ref; // Null out the player reference when the player is disposed from
// outside the component.
if (type === 'in-page') {
ref.one('dispose', function () {
_this2.player = null;
});
} // Add a REACT_PLAYER_LOADER property to bcinfo to indicate this player
// was loaded via that mechanism.
if (ref.bcinfo) {
ref.bcinfo.REACT_PLAYER_LOADER = true;
} // Call a user-provided onSuccess callback.
if (typeof userSuccess === 'function') {
userSuccess({
ref: ref,
type: type
});
}
},
onFailure: function onFailure(error) {
// Ignore errors when not mounted.
if (!_this2.isMounted_) {
return;
} // Call a user-provided onFailure callback.
if (typeof userFailure === 'function') {
userFailure(error);
return;
} // Fall back to throwing an error;
throw new Error(error);
}
}); // Delete props that are not meant to be passed to player-loader.
delete options.attrs;
delete options.baseUrl;
delete options.manualReloadFromPropChanges; // If a base URL is provided, it should only apply to this player load.
// This means we need to back up the original base URL and restore it
// _after_ we call player loader.
var originalBaseUrl = brightcovePlayerLoader.getBaseUrl();
if (this.props.baseUrl) {
brightcovePlayerLoader.setBaseUrl(this.props.baseUrl);
}
brightcovePlayerLoader(options);
brightcovePlayerLoader.setBaseUrl(originalBaseUrl);
}
/**
* Disposes the current player, if there is one.
*/
;
_proto.disposePlayer = function disposePlayer() {
// Nothing to dispose.
if (!this.player) {
return;
} // Dispose an in-page player.
if (this.player.dispose) {
this.player.dispose(); // Dispose an iframe player.
} else if (this.player.parentNode) {
this.player.parentNode.removeChild(this.player);
} // Null out the player reference.
this.player = null;
}
/**
* Find the index of the `playlistVideoId` prop within the player's playlist.
*
* @param {Object[]} playlist
* An array of playlist item objects.
*
* @return {number}
* The index of the `playlistVideoId` or `-1` if the player has been
* disposed, is not using the playlist plugin, or if not found.
*/
;
_proto.findPlaylistVideoIdIndex_ = function findPlaylistVideoIdIndex_(playlist) {
var playlistVideoId = this.props.playlistVideoId;
if (Array.isArray(playlist) && playlistVideoId) {
for (var i = 0; i < playlist.length; i++) {
var _playlist$i = playlist[i],
id = _playlist$i.id,
referenceId = _playlist$i.referenceId;
if (id === playlistVideoId || "ref:" + referenceId === playlistVideoId) {
return i;
}
}
}
return -1;
}
/**
* Create a Playback API callback function for the component's player.
*
* @private
* @param {string} requestType
* The Playback API request type (e.g. "video" or "playlist").
*
* @param {Object} changes
* An object. The keys of this object are the props that changed.
*
* @return {Function}
* A callback for the Playback API request.
*/
;
_proto.createPlaybackAPICallback_ = function createPlaybackAPICallback_(requestType, changes) {
var _this3 = this;
return function (err, data) {
if (err) {
logError(err);
return;
} // If the playlistVideoId changed and this is a playlist request, we
// need to search through the playlist items to find the correct
// starting index.
if (requestType === 'playlist' && changes.playlistVideoId) {
var i = _this3.findPlaylistVideoIdIndex_(data);
if (i > -1) {
data.startingIndex = i;
}
}
_this3.player.catalog.load(data);
};
}
/**
* Update the player based on changes to certain props that do not require
* a full player dispose/recreate.
*
* @param {Object} changes
* An object. The keys of this object are the props that changed.
*/
;
_proto.updatePlayer = function updatePlayer(changes) {
// No player exists, player is disposed, or not using the catalog
if (!this.player || !this.player.el || !this.player.el()) {
return;
} // If the player is using the catalog plugin, we _may_ populate this
// variable with an object.
var catalogParams;
if (this.player.usingPlugin('catalog')) {
// There is a new catalog sequence request. This takes precedence over
// other catalog updates because it is a different call.
if (changes.catalogSequence && this.props.catalogSequence) {
var callback = this.createPlaybackAPICallback_('sequence', changes);
this.player.catalog.getLazySequence(this.props.catalogSequence, callback, this.props.adConfigId);
return;
}
if (changes.videoId && this.props.videoId) {
catalogParams = {
type: 'video',
id: this.props.videoId
};
} else if (changes.playlistId && this.props.playlistId) {
catalogParams = {
type: 'playlist',
id: this.props.playlistId
};
} else if (changes.catalogSearch && this.props.catalogSearch) {
catalogParams = {
type: 'search',
q: this.props.catalogSearch
};
}
} // If `catalogParams` is `undefined` here, that means the player either
// does not have the catalog plugin or no valid catalog request can be made.
if (catalogParams) {
if (this.props.adConfigId) {
catalogParams.adConfigId = this.props.adConfigId;
}
if (this.props.deliveryConfigId) {
catalogParams.deliveryConfigId = this.props.deliveryConfigId;
} // We use the callback style here to make tests simpler in IE11 (no need
// for a Promise polyfill).
var _callback = this.createPlaybackAPICallback_(catalogParams.type, changes);
this.player.catalog.get(catalogParams, _callback); // If no catalog request is being made, we may still need to update the
// playlist selected video.
} else if (changes.playlistVideoId && this.props.playlistVideoId && this.player.usingPlugin('playlist')) {
var i = this.findPlaylistVideoIdIndex_(this.player.playlist());
if (i > -1) {
this.player.playlist.currentItem(i);
}
}
}
/**
* Called just after the component has mounted.
*/
;
_proto.componentDidMount = function componentDidMount() {
this.isMounted_ = true;
this.loadPlayer();
}
/**
* Called when the component props are updated.
*
* Some prop changes may trigger special behavior (see `propChangeHandlers`),
* but if ANY prop is changed that is NOT handled, the player will be
* disposed/recreated entirely.
*
* @param {Object} prevProps
* The previous props state before change.
*/
;
_proto.componentDidUpdate = function componentDidUpdate(prevProps) {
var _this4 = this;
// Calculate the prop changes.
var changes = Object.keys(prevProps).reduce(function (acc, key) {
var previous = prevProps[key];
var current = _this4.props[key]; // Do not compare functions
if (typeof current === 'function') {
return acc;
}
if (typeof current === 'object' && current !== null) {
if (JSON.stringify(current) !== JSON.stringify(previous)) {
acc[key] = true;
}
return acc;
}
if (current !== previous) {
acc[key] = true;
}
return acc;
}, {});
if (!this.props.manualReloadFromPropChanges) {
// Dispose and recreate the player if any changed keys cannot be handled.
if (Object.keys(changes).some(function (k) {
return UPDATEABLE_PROPS.indexOf(k) === -1;
})) {
this.loadPlayer();
return;
}
}
this.updatePlayer(changes);
}
/**
* Called just before a component unmounts. Disposes the player.
*/
;
_proto.componentWillUnmount = function componentWillUnmount() {
this.isMounted_ = false;
this.disposePlayer();
}
/**
* Renders the component.
*
* @return {ReactElement}
* The react element to render.
*/
;
_proto.render = function render() {
var props = _extends({
className: 'brightcove-react-player-loader'
}, this.props.attrs, {
ref: this.setRefNode
});
return React.createElement('div', props);
};
return ReactPlayerLoader;
}(React.Component);
export default ReactPlayerLoader;