videojs-playlist
Version:
Playlist plugin for Video.js
519 lines (443 loc) • 13.5 kB
JavaScript
/**
* 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;
})));