UNPKG

videojs-playlist

Version:
519 lines (443 loc) 13.5 kB
/** * videojs-playlist * @version 4.0.0 * @copyright 2017 Brightcove, Inc. * @license Apache-2.0 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) : typeof define === 'function' && define.amd ? define(['video.js'], factory) : (global.videojsPlaylist = factory(global.videojs)); }(this, (function (videojs) { 'use strict'; videojs = 'default' in videojs ? videojs['default'] : videojs; var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; var win; if (typeof window !== "undefined") { win = window; } else if (typeof commonjsGlobal !== "undefined") { win = commonjsGlobal; } else if (typeof self !== "undefined"){ win = self; } else { win = {}; } var window_1 = win; /** * Validates a number of seconds to use as the auto-advance delay. * * @private * @param {number} s * The number to check * * @return {boolean} * Whether this is a valid second or not */ var validSeconds = function validSeconds(s) { return typeof s === 'number' && !isNaN(s) && s >= 0 && s < Infinity; }; /** * Resets the auto-advance behavior of a player. * * @param {Player} player * The player to reset the behavior on */ var reset = function reset(player) { if (player.playlist.autoadvance_.timeout) { window_1.clearTimeout(player.playlist.autoadvance_.timeout); } if (player.playlist.autoadvance_.trigger) { player.off('ended', player.playlist.autoadvance_.trigger); } player.playlist.autoadvance_.timeout = null; player.playlist.autoadvance_.trigger = null; }; /** * Sets up auto-advance behavior on a player. * * @param {Player} player * the current player * * @param {number} delay * The number of seconds to wait before each auto-advance. * * @return {undefined} * Used to short circuit function logic */ var setup = function setup(player, delay) { reset(player); // Before queuing up new auto-advance behavior, check if `seconds` was // called with a valid value. if (!validSeconds(delay)) { return; } player.playlist.autoadvance_.trigger = function () { player.playlist.autoadvance_.timeout = window_1.setTimeout(function () { reset(player); player.playlist.next(); }, delay * 1000); }; player.one('ended', player.playlist.autoadvance_.trigger); }; /** * Removes all remote text tracks from a player. * * @param {Player} player * The player to clear tracks on */ var clearTracks = function clearTracks(player) { var tracks = player.remoteTextTracks(); var i = tracks && tracks.length || 0; // This uses a `while` loop rather than `forEach` because the // `TextTrackList` object is a live DOM list (not an array). while (i--) { player.removeRemoteTextTrack(tracks[i]); } }; /** * Plays an item on a player's playlist. * * @param {Player} player * The player to play the item on * * @param {number} delay * The number of seconds to wait before each auto-advance. * * @param {Object} item * A source from the playlist. * * @return {Player} * The player that is now playing the item */ var playItem = function playItem(player, delay, item) { var replay = !player.paused() || player.ended(); player.trigger('beforeplaylistitem', item); player.poster(item.poster || ''); player.src(item.sources); clearTracks(player); (item.textTracks || []).forEach(player.addRemoteTextTrack.bind(player)); player.trigger('playlistitem', item); if (replay) { player.play(); } setup(player, delay); return player; }; 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; }; // Lightweight Object.assign alternative. var assign = function assign(target, source) { for (var key in source) { if (source.hasOwnProperty(key)) { target[key] = source[key]; } } }; /** * Given two sources, check to see whether the two sources are equal. * If both source urls have a protocol, the protocols must match, otherwise, protocols * are ignored. * * @private * @param {string|Object} source1 * The first source * * @param {string|Object} source2 * The second source * * @return {boolean} * The result */ var sourceEquals = function sourceEquals(source1, source2) { var src1 = source1; var src2 = source2; if ((typeof source1 === 'undefined' ? 'undefined' : _typeof(source1)) === 'object') { src1 = source1.src; } if ((typeof source2 === 'undefined' ? 'undefined' : _typeof(source2)) === 'object') { src2 = source2.src; } if (/^\/\//.test(src1)) { src2 = src2.slice(src2.indexOf('//')); } if (/^\/\//.test(src2)) { src1 = src1.slice(src1.indexOf('//')); } return src1 === src2; }; /** * Look through an array of playlist items for a specific `source`; * checking both the value of elements and the value of their `src` * property. * * @private * @param {Array} arr * An array of playlist items to look through * * @param {string} src * The source to look for * * @return {number} * The index of that source or -1 */ var indexInSources = function indexInSources(arr, src) { for (var i = 0; i < arr.length; i++) { var sources = arr[i].sources; if (Array.isArray(sources)) { for (var j = 0; j < sources.length; j++) { var source = sources[j]; if (source && sourceEquals(source, src)) { return i; } } } } return -1; }; /** * Factory function for creating new playlist implementation on the given player. * * API summary: * * playlist(['a', 'b', 'c']) // setter * playlist() // getter * playlist.currentItem() // getter, 0 * playlist.currentItem(1) // setter, 1 * playlist.next() // 'c' * playlist.previous() // 'b' * playlist.first() // 'a' * playlist.last() // 'c' * playlist.autoadvance(5) // 5 second delay * playlist.autoadvance() // cancel autoadvance * * @param {Player} player * The current player * * @param {Array=} initialList * If given, an initial list of sources with which to populate * the playlist. * * @param {number=} initialIndex * If given, the index of the item in the list that should * be loaded first. If -1, no video is loaded. If omitted, The * the first video is loaded. * * @return {Function} * Returns the playlist function specific to the given player. */ var factory = function factory(player, initialList) { var initialIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var list = Array.isArray(initialList) ? initialList.slice() : []; /** * Get/set the playlist for a player. * * This function is added as an own property of the player and has its * own methods which can be called to manipulate the internal state. * * @param {Array} [newList] * If given, a new list of sources with which to populate the * playlist. Without this, the function acts as a getter. * * @param {number} [newIndex] * If given, the index of the item in the list that should * be loaded first. If -1, no video is loaded. If omitted, The * the first video is loaded. * * @return {Array} * The playlist */ var playlist = player.playlist = function (newList) { var newIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (Array.isArray(newList)) { list = newList.slice(); if (newIndex !== -1) { playlist.currentItem(newIndex); } playlist.changeTimeout_ = window_1.setTimeout(function () { player.trigger('playlistchange'); }, 0); } // Always return a shallow clone of the playlist list. return list.slice(); }; player.on('loadstart', function () { if (playlist.currentItem() === -1) { reset(player); } }); player.on('dispose', function () { window_1.clearTimeout(playlist.changeTimeout_); }); assign(playlist, { currentIndex_: -1, player_: player, autoadvance_: {}, repeat_: false, /** * Get or set the current item in the playlist. * * @param {number} [index] * If given as a valid value, plays the playlist item at that index. * * @return {number} * The current item index. */ currentItem: function currentItem(index) { if (typeof index === 'number' && playlist.currentIndex_ !== index && index >= 0 && index < list.length) { playlist.currentIndex_ = index; playItem(playlist.player_, playlist.autoadvance_.delay, list[playlist.currentIndex_]); } else { playlist.currentIndex_ = playlist.indexOf(playlist.player_.currentSrc() || ''); } return playlist.currentIndex_; }, /** * Checks if the playlist contains a value. * * @param {string|Object|Array} value * The value to check * * @return {boolean} * The result */ contains: function contains(value) { return playlist.indexOf(value) !== -1; }, /** * Gets the index of a value in the playlist or -1 if not found. * * @param {string|Object|Array} value * The value to find the index of * * @return {number} * The index or -1 */ indexOf: function indexOf(value) { if (typeof value === 'string') { return indexInSources(list, value); } var sources = Array.isArray(value) ? value : value.sources; for (var i = 0; i < sources.length; i++) { var source = sources[i]; if (typeof source === 'string') { return indexInSources(list, source); } else if (source.src) { return indexInSources(list, source.src); } } return -1; }, /** * Plays the first item in the playlist. * * @return {Object|undefined} * Returns undefined and has no side effects if the list is empty. */ first: function first() { if (list.length) { return list[playlist.currentItem(0)]; } playlist.currentIndex_ = -1; }, /** * Plays the last item in the playlist. * * @return {Object|undefined} * Returns undefined and has no side effects if the list is empty. */ last: function last() { if (list.length) { return list[playlist.currentItem(list.length - 1)]; } playlist.currentIndex_ = -1; }, /** * Plays the next item in the playlist. * * @return {Object|undefined} * Returns undefined and has no side effects if on last item. */ next: function next() { var nextIndex = void 0; // Repeat if (playlist.repeat_) { nextIndex = playlist.currentIndex_ + 1; if (nextIndex > list.length - 1) { nextIndex = 0; } // Don't go past the end of the playlist. } else { nextIndex = Math.min(playlist.currentIndex_ + 1, list.length - 1); } // Make the change if (nextIndex !== playlist.currentIndex_) { return list[playlist.currentItem(nextIndex)]; } }, /** * Plays the previous item in the playlist. * * @return {Object|undefined} * Returns undefined and has no side effects if on first item. */ previous: function previous() { // Make sure we don't go past the start of the playlist. var index = Math.max(playlist.currentIndex_ - 1, 0); if (index !== playlist.currentIndex_) { return list[playlist.currentItem(index)]; } }, /** * Sets up auto-advance on the playlist. * * @param {number} delay * The number of seconds to wait before each auto-advance. */ autoadvance: function autoadvance(delay) { playlist.autoadvance_.delay = delay; setup(playlist.player_, delay); }, /** * Sets `repeat` option, which makes the "next" video of the last video in the * playlist be the first video in the playlist. * * @param {boolean=} val * The value to set repeat to * * @return {boolean} * The current value of repeat */ repeat: function repeat(val) { if (val !== undefined) { if (typeof val !== 'boolean') { videojs.log.error('Invalid value for repeat', val); } else { playlist.repeat_ = val; } } return playlist.repeat_; } }); playlist.currentItem(initialIndex); return playlist; }; // Video.js 5/6 cross-compatible. var registerPlugin = videojs.registerPlugin || videojs.plugin; /** * The video.js playlist plugin. Invokes the playlist-maker to create a * playlist function on the specific player. * * @param {Array} list * a list of sources * * @param {number} item * The index to start at */ var plugin = function plugin(list, item) { factory(this, list, item); }; registerPlugin('playlist', plugin); return plugin; })));