/*! @name videojs-playlist-ui @version 3.5.2 @license Apache-2.0 */ import document from 'global/document'; import videojs from 'video.js'; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var version = "3.5.2"; var dom = videojs.dom || videojs; var registerPlugin = videojs.registerPlugin || videojs.plugin; // Array#indexOf analog for IE8 var indexOf = function indexOf(array, target) { for (var i = 0, length = array.length; i < length; i++) { if (array[i] === target) { return i; } } return -1; }; // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js var supportsCssPointerEvents = function () { var element = document.createElement('x'); element.style.cssText = 'pointer-events:auto'; return element.style.pointerEvents === 'auto'; }(); var defaults = { className: 'vjs-playlist', playOnSelect: false, supportsCssPointerEvents: supportsCssPointerEvents }; // we don't add `vjs-playlist-now-playing` in addSelectedClass // so it won't conflict with `vjs-icon-play // since it'll get added when we mouse out var addSelectedClass = function addSelectedClass(el) { el.addClass('vjs-selected'); }; var removeSelectedClass = function removeSelectedClass(el) { el.removeClass('vjs-selected'); if (el.thumbnail) { dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing'); } }; var upNext = function upNext(el) { el.addClass('vjs-up-next'); }; var notUpNext = function notUpNext(el) { el.removeClass('vjs-up-next'); }; var createThumbnail = function createThumbnail(thumbnail) { if (!thumbnail) { var placeholder = document.createElement('div'); placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder'; return placeholder; } var picture = document.createElement('picture'); picture.className = 'vjs-playlist-thumbnail'; if (typeof thumbnail === 'string') { // simple thumbnails var img = document.createElement('img'); img.src = thumbnail; img.alt = ''; picture.appendChild(img); } else { // responsive thumbnails // additional variations of a are specified as // elements for (var i = 0; i < thumbnail.length - 1; i++) { var _variant = thumbnail[i]; var source = document.createElement('source'); // transfer the properties of each variant onto a for (var prop in _variant) { source[prop] = _variant[prop]; } picture.appendChild(source); } // the default version of a is specified by an var variant = thumbnail[thumbnail.length - 1]; var _img = document.createElement('img'); _img.alt = ''; for (var _prop in variant) { _img[_prop] = variant[_prop]; } picture.appendChild(_img); } return picture; }; var Component = videojs.getComponent('Component'); var PlaylistMenuItem = /*#__PURE__*/ function (_Component) { _inheritsLoose(PlaylistMenuItem, _Component); function PlaylistMenuItem(player, playlistItem, settings) { var _this; if (!playlistItem.item) { throw new Error('Cannot construct a PlaylistMenuItem without an item option'); } _this = _Component.call(this, player, playlistItem) || this; _this.item = playlistItem.item; _this.playOnSelect = settings.playOnSelect; _this.emitTapEvents(); _this.on(['click', 'tap'], _this.switchPlaylistItem_); _this.on('keydown', _this.handleKeyDown_); return _this; } var _proto = PlaylistMenuItem.prototype; _proto.handleKeyDown_ = function handleKeyDown_(event) { // keycode 13 is // keycode 32 is if (event.which === 13 || event.which === 32) { this.switchPlaylistItem_(); } }; _proto.switchPlaylistItem_ = function switchPlaylistItem_(event) { this.player_.playlist.currentItem(indexOf(this.player_.playlist(), this.item)); if (this.playOnSelect) { this.player_.play(); } }; _proto.createEl = function createEl() { var li = document.createElement('li'); var item = this.options_.item; if (typeof item.data === 'object') { var dataKeys = Object.keys(item.data); dataKeys.forEach(function (key) { var value = item.data[key]; li.dataset[key] = value; }); } li.className = 'vjs-playlist-item'; li.setAttribute('tabIndex', 0); // Thumbnail image this.thumbnail = createThumbnail(item.thumbnail); li.appendChild(this.thumbnail); // Duration if (item.duration) { var duration = document.createElement('time'); var time = videojs.formatTime(item.duration); duration.className = 'vjs-playlist-duration'; duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S'); duration.appendChild(document.createTextNode(time)); li.appendChild(duration); } // Now playing var nowPlayingEl = document.createElement('span'); var nowPlayingText = this.localize('Now Playing'); nowPlayingEl.className = 'vjs-playlist-now-playing-text'; nowPlayingEl.appendChild(document.createTextNode(nowPlayingText)); nowPlayingEl.setAttribute('title', nowPlayingText); this.thumbnail.appendChild(nowPlayingEl); // Title container contains title and "up next" var titleContainerEl = document.createElement('div'); titleContainerEl.className = 'vjs-playlist-title-container'; this.thumbnail.appendChild(titleContainerEl); // Up next var upNextEl = document.createElement('span'); var upNextText = this.localize('Up Next'); upNextEl.className = 'vjs-up-next-text'; upNextEl.appendChild(document.createTextNode(upNextText)); upNextEl.setAttribute('title', upNextText); titleContainerEl.appendChild(upNextEl); // Video title var titleEl = document.createElement('cite'); var titleText = item.name || this.localize('Untitled Video'); titleEl.className = 'vjs-playlist-name'; titleEl.appendChild(document.createTextNode(titleText)); titleEl.setAttribute('title', titleText); titleContainerEl.appendChild(titleEl); return li; }; return PlaylistMenuItem; }(Component); var PlaylistMenu = /*#__PURE__*/ function (_Component2) { _inheritsLoose(PlaylistMenu, _Component2); function PlaylistMenu(player, options) { var _this2; if (!player.playlist) { throw new Error('videojs-playlist is required for the playlist component'); } _this2 = _Component2.call(this, player, options) || this; _this2.items = []; if (options.horizontal) { _this2.addClass('vjs-playlist-horizontal'); } else { _this2.addClass('vjs-playlist-vertical'); } // If CSS pointer events aren't supported, we have to prevent // clicking on playlist items during ads with slightly more // invasive techniques. Details in the stylesheet. if (options.supportsCssPointerEvents) { _this2.addClass('vjs-csspointerevents'); } _this2.createPlaylist_(); if (!videojs.browser.TOUCH_ENABLED) { _this2.addClass('vjs-mouse'); } _this2.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], function (event) { _this2.update(); }); // Keep track of whether an ad is playing so that the menu // appearance can be adapted appropriately _this2.on(player, 'adstart', function () { _this2.addClass('vjs-ad-playing'); }); _this2.on(player, 'adend', function () { _this2.removeClass('vjs-ad-playing'); }); _this2.on('dispose', function () { _this2.empty_(); player.playlistMenu = null; }); _this2.on(player, 'dispose', function () { _this2.dispose(); }); return _this2; } var _proto2 = PlaylistMenu.prototype; _proto2.createEl = function createEl() { return dom.createEl('div', { className: this.options_.className }); }; _proto2.empty_ = function empty_() { if (this.items && this.items.length) { this.items.forEach(function (i) { return i.dispose(); }); this.items.length = 0; } }; _proto2.createPlaylist_ = function createPlaylist_() { var playlist = this.player_.playlist() || []; var list = this.el_.querySelector('.vjs-playlist-item-list'); var overlay = this.el_.querySelector('.vjs-playlist-ad-overlay'); if (!list) { list = document.createElement('ol'); list.className = 'vjs-playlist-item-list'; this.el_.appendChild(list); } this.empty_(); // create new items for (var i = 0; i < playlist.length; i++) { var item = new PlaylistMenuItem(this.player_, { item: playlist[i] }, this.options_); this.items.push(item); list.appendChild(item.el_); } // Inject the ad overlay. IE<11 doesn't support "pointer-events: // none" so we use this element to block clicks during ad // playback. if (!overlay) { overlay = document.createElement('li'); overlay.className = 'vjs-playlist-ad-overlay'; list.appendChild(overlay); } else { // Move overlay to end of list list.appendChild(overlay); } // select the current playlist item var selectedIndex = this.player_.playlist.currentItem(); if (this.items.length && selectedIndex >= 0) { addSelectedClass(this.items[selectedIndex]); var thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail'); if (thumbnail) { dom.addClass(thumbnail, 'vjs-playlist-now-playing'); } } }; _proto2.update = function update() { // replace the playlist items being displayed, if necessary var playlist = this.player_.playlist(); if (this.items.length !== playlist.length) { // if the menu is currently empty or the state is obviously out // of date, rebuild everything. this.createPlaylist_(); return; } for (var i = 0; i < this.items.length; i++) { if (this.items[i].item !== playlist[i]) { // if any of the playlist items have changed, rebuild the // entire playlist this.createPlaylist_(); return; } } // the playlist itself is unchanged so just update the selection var currentItem = this.player_.playlist.currentItem(); for (var _i = 0; _i < this.items.length; _i++) { var item = this.items[_i]; if (_i === currentItem) { addSelectedClass(item); if (document.activeElement !== item.el()) { dom.addClass(item.thumbnail, 'vjs-playlist-now-playing'); } notUpNext(item); } else if (_i === currentItem + 1) { removeSelectedClass(item); upNext(item); } else { removeSelectedClass(item); notUpNext(item); } } }; return PlaylistMenu; }(Component); /** * Returns a boolean indicating whether an element has child elements. * * Note that this is distinct from whether it has child _nodes_. * * @param {HTMLElement} el * A DOM element. * * @return {boolean} * Whether the element has child elements. */ var hasChildEls = function hasChildEls(el) { for (var i = 0; i < el.childNodes.length; i++) { if (dom.isEl(el.childNodes[i])) { return true; } } return false; }; /** * Finds the first empty root element. * * @param {string} className * An HTML class name to search for. * * @return {HTMLElement} * A DOM element to use as the root for a playlist. */ var findRoot = function findRoot(className) { var all = document.querySelectorAll('.' + className); var el; for (var i = 0; i < all.length; i++) { if (!hasChildEls(all[i])) { el = all[i]; break; } } return el; }; /** * Initialize the plugin on a player. * * @param {Object} [options] * An options object. * * @param {HTMLElement} [options.el] * A DOM element to use as a root node for the playlist. * * @param {string} [options.className] * An HTML class name to use to find a root node for the playlist. * * @param {boolean} [options.playOnSelect = false] * If true, will attempt to begin playback upon selecting a new * playlist item in the UI. */ var playlistUi = function playlistUi(options) { var player = this; if (!player.playlist) { throw new Error('videojs-playlist plugin is required by the videojs-playlist-ui plugin'); } if (dom.isEl(options)) { videojs.log.warn('videojs-playlist-ui: Passing an element directly to playlistUi() is deprecated, use the "el" option instead!'); options = { el: options }; } options = videojs.mergeOptions(defaults, options); // If the player is already using this plugin, remove the pre-existing // PlaylistMenu, but retain the element and its location in the DOM because // it will be re-used. if (player.playlistMenu) { var el = player.playlistMenu.el(); // Catch cases where the menu may have been disposed elsewhere or the // element removed from the DOM. if (el) { var parentNode = el.parentNode; var nextSibling = el.nextSibling; // Disposing the menu will remove `el` from the DOM, but we need to // empty it ourselves to be sure. player.playlistMenu.dispose(); dom.emptyEl(el); // Put the element back in its place. if (nextSibling) { parentNode.insertBefore(el, nextSibling); } else { parentNode.appendChild(el); } options.el = el; } } if (!dom.isEl(options.el)) { options.el = findRoot(options.className); } player.playlistMenu = new PlaylistMenu(player, options); }; // register components videojs.registerComponent('PlaylistMenu', PlaylistMenu); videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem); // register the plugin registerPlugin('playlistUi', playlistUi); playlistUi.VERSION = version; export default playlistUi;