1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-03 01:39:24 +02:00
This commit is contained in:
Daniel Neto 2023-07-02 19:30:57 -03:00
parent ed9048ec37
commit 14f492d68f
60 changed files with 27146 additions and 0 deletions

24
node_modules/.package-lock.json generated vendored
View file

@ -2024,6 +2024,30 @@
"video.js": "5.x || 6.x || 7.x || *" "video.js": "5.x || 6.x || 7.x || *"
} }
}, },
"node_modules/videojs-playlist": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.0.tgz",
"integrity": "sha512-p5ohld6Kom9meYCcEVYj0JVS2MBL2XxMiU+IDB/xKpDOspFAHrERHrZEBoiJZc/mCfHixZBNgj1vWRgYsVVsrw==",
"dependencies": {
"global": "^4.3.2",
"video.js": "^6 || ^7 || ^8"
},
"engines": {
"node": ">=4.4.0"
}
},
"node_modules/videojs-playlist-ui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/videojs-playlist-ui/-/videojs-playlist-ui-5.0.0.tgz",
"integrity": "sha512-Ct6uzWO7FittnXqWU0mnF+ahE1IYvXYVTu5O1qGPW5BkUdW+j0b+wwbiQp3Wj49MXB2A/gKfgIpm4QFVRKq8kw==",
"dependencies": {
"global": "^4.4.0"
},
"peerDependencies": {
"video.js": "^8.0.0",
"videojs-playlist": "^5.1.0"
}
},
"node_modules/videojs-seek-buttons": { "node_modules/videojs-seek-buttons": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz", "resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz",

30
node_modules/videojs-playlist-ui/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,30 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have Node.js 4.8 or higher and npm installed.
1. Fork this repository and clone your fork
1. Install dependencies: `npm install`
1. Run a development server: `npm start`
### Making Changes
Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
### Running Tests
Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
- In all available and supported browsers: `npm test`
- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
[karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md

13
node_modules/videojs-playlist-ui/LICENSE generated vendored Normal file
View file

@ -0,0 +1,13 @@
Copyright Brightcove, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

109
node_modules/videojs-playlist-ui/README.md generated vendored Normal file
View file

@ -0,0 +1,109 @@
# videojs-playlist-ui
[![NPM](https://nodei.co/npm/videojs-playlist-ui.png?downloads=true&downloadRank=true)](https://nodei.co/npm/videojs-playlist-ui/)
A playlist video picker for video.js and videojs-playlist
Maintenance Status: Stable
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Getting Started](#getting-started)
- [Root Element](#root-element)
- [Using Automatic Discovery (default, example)](#using-automatic-discovery-default-example)
- [Using a Custom Class (example)](#using-a-custom-class-example)
- [Using a Custom Element (example)](#using-a-custom-element-example)
- [Other Options](#other-options)
- [`className`](#classname)
- [playOnSelect](#playonselect)
- [Playlists and Advertisements](#playlists-and-advertisements)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Getting Started
Include the plugin script in your page, and a placeholder list element with the class `vjs-playlist` to house the playlist menu:
```html
<!-- Include the playlist menu styles somewhere in your page -->
<link href="videojs-playlist-ui.css" rel="stylesheet">
<!-- Your player will be created here: -->
<video-js data-setup='{}' controls></video-js>
<!-- The playlist menu will be built automatically in here -->
<div class="vjs-playlist"></div>
<!-- Include video.js, the videojs-playlist plugin and this plugin -->
<script src="video.js"></script>
<script src="videojs-playlist.js"></script>
<script src="videojs-playlist-ui.js"></script>
<script>
// Initialize the player
const player = videojs(document.querySelector('video-js'));
// Initialize the plugin and render the playlist
player.playlistUi();
</script>
```
There's also a [working example](example.html) of the plugin you can check out if you're having trouble.
## Root Element
Before this plugin will work at all, it needs an element in the DOM to which to attach itself. There are three ways to find or provide this element.
> **NOTE:** In v2.x of this plugin, the root element was expected to be a list element (i.e., `<ol>` or `<ul>`). As of v3.x, the plugin creates a list; so, this root element _must_ be a non-list container element (e.g., `<div>`).
### Using Automatic Discovery (default, [example](example.html))
By default, the plugin will search for the first element in the DOM with the `vjs-playlist` class.
To defend against problems caused by multiple playlist players on a page, the plugin will only use an element with the `vjs-playlist` class if that element has not been used by another player's playlist.
### Using a Custom Class ([example](example-custom-class.html))
A custom `className` option can be passed to override the class the plugin will search for to find the root element. The same defense against multiple playlist players is reused in this case.
```js
player.playlistUi({
className: 'hello-world'
});
```
### Using a Custom Element ([example](example-custom-element.html))
A custom element can be passed using the `el` option to explicitly define a specific root element.
```js
player.playlistUi({
el: document.getElementById('hello-world')
});
```
## Other Options
The options passed to the plugin are passed to the internal `PlaylistMenu` [video.js Component][components]; so, you may pass in [any option][components-options] that is accepted by a component.
In addition, the options object may contain the following specialized properties:
#### `className`
Type: `string`
Default: `"vjs-playlist"`
As mentioned [above](#using-a-custom-class), the name of the class to search for to populate the playlist menu.
#### playOnSelect
Type: `boolean`
Default: `false`
The default behavior is that the play state is expected to stay the same between videos. If the player is playing when switching playlist items, continue playing. If paused, stay paused.
When this boolean is set to `true`, clicking on the playlist menu items will always play the video.
## Playlists and Advertisements
The `PlaylistMenu` automatically adapts to ad integrations based on [videojs-contrib-ads][contrib-ads]. When a linear ad is being played, the menu will darken and stop responding to click or touch events. If you'd prefer to allow your viewers to change videos during ad playback, you can override this behavior through CSS. You will also need to make sure that your ad integration is properly cancelled and cleaned up before switching -- consult the documentation for your ad library for details on how to do that.
[components]: https://videojs.com/guides/components/
[components-options]: https://videojs.com/guides/components/#using-options
[contrib-ads]: https://github.com/videojs/videojs-contrib-ads

5
node_modules/videojs-playlist-ui/dist/lang/ar.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('ar', {
"Now Playing": "قيد التشغيل الآن",
"Up Next": "التالي",
"Untitled Video": "مقطع فيديو بدون عنوان"
});

5
node_modules/videojs-playlist-ui/dist/lang/de.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('de', {
"Now Playing": "Aktuelle Wiedergabe",
"Up Next": "Als Nächstes",
"Untitled Video": "Video ohne Titel"
});

5
node_modules/videojs-playlist-ui/dist/lang/en.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('en', {
"Now Playing": "Now Playing",
"Up Next": "Up Next",
"Untitled Video": "Untitled Video"
});

5
node_modules/videojs-playlist-ui/dist/lang/es.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('es', {
"Now Playing": "Reproduciendo",
"Up Next": "A continuación:",
"Untitled Video": "Vídeo sin título"
});

5
node_modules/videojs-playlist-ui/dist/lang/fr.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('fr', {
"Now Playing": "En cours de lecture",
"Up Next": "À suivre",
"Untitled Video": "Vidéo sans titre"
});

5
node_modules/videojs-playlist-ui/dist/lang/ja.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('ja', {
"Now Playing": "現在再生中",
"Up Next": "次の動画",
"Untitled Video": "無題の動画"
});

5
node_modules/videojs-playlist-ui/dist/lang/ko.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
videojs.addLanguage('ko', {
"Now Playing": "지금 재생 중",
"Up Next": "다음",
"Untitled Video": "제목 없는 비디오"
});

View file

@ -0,0 +1,5 @@
videojs.addLanguage('zh-Hans', {
"Now Playing": "正在播放",
"Up Next": "播放下一个",
"Untitled Video": "无标题视频"
});

View file

@ -0,0 +1,5 @@
videojs.addLanguage('zh-Hant', {
"Now Playing": "正在播放",
"Up Next": "播放下一個",
"Untitled Video": "未命名視訊"
});

View file

@ -0,0 +1,467 @@
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
'use strict';
var document = require('global/document');
var videojs = require('video.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var document__default = /*#__PURE__*/_interopDefaultLegacy(document);
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
var version = "5.0.0";
const Component$1 = videojs__default["default"].getComponent('Component');
const createThumbnail = function (thumbnail) {
if (!thumbnail) {
const placeholder = document__default["default"].createElement('div');
placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder';
return placeholder;
}
const picture = document__default["default"].createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {
// simple thumbnails
const img = document__default["default"].createElement('img');
img.loading = 'lazy';
img.src = thumbnail;
img.alt = '';
picture.appendChild(img);
} else {
// responsive thumbnails
// additional variations of a <picture> are specified as
// <source> elements
for (let i = 0; i < thumbnail.length - 1; i++) {
const variant = thumbnail[i];
const source = document__default["default"].createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];
const img = document__default["default"].createElement('img');
img.loading = 'lazy';
img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {
if (!playlistItem.item) {
throw new Error('Cannot construct a PlaylistMenuItem without an item option');
}
playlistItem.showDescription = settings.showDescription;
super(player, playlistItem);
this.item = playlistItem.item;
this.playOnSelect = settings.playOnSelect;
this.emitTapEvents();
this.on(['click', 'tap'], this.switchPlaylistItem_);
this.on('keydown', this.handleKeyDown_);
}
handleKeyDown_(event) {
// keycode 13 is <Enter>
// keycode 32 is <Space>
if (event.which === 13 || event.which === 32) {
this.switchPlaylistItem_();
}
}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {
this.player_.play();
}
}
createEl() {
const li = document__default["default"].createElement('li');
const item = this.options_.item;
const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {
const dataKeys = Object.keys(item.data);
dataKeys.forEach(key => {
const 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) {
const duration = document__default["default"].createElement('time');
const time = videojs__default["default"].time.formatTime(item.duration);
duration.className = 'vjs-playlist-duration';
duration.setAttribute('datetime', 'PT0H0M' + item.duration + 'S');
duration.appendChild(document__default["default"].createTextNode(time));
li.appendChild(duration);
}
// Now playing
const nowPlayingEl = document__default["default"].createElement('span');
const nowPlayingText = this.localize('Now Playing');
nowPlayingEl.className = 'vjs-playlist-now-playing-text';
nowPlayingEl.appendChild(document__default["default"].createTextNode(nowPlayingText));
nowPlayingEl.setAttribute('title', nowPlayingText);
this.thumbnail.appendChild(nowPlayingEl);
// Title container contains title and "up next"
const titleContainerEl = document__default["default"].createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document__default["default"].createElement('span');
const upNextText = this.localize('Up Next');
upNextEl.className = 'vjs-up-next-text';
upNextEl.appendChild(document__default["default"].createTextNode(upNextText));
upNextEl.setAttribute('title', upNextText);
titleContainerEl.appendChild(upNextEl);
// Video title
const titleEl = document__default["default"].createElement('cite');
const titleText = item.name || this.localize('Untitled Video');
titleEl.className = 'vjs-playlist-name';
titleEl.appendChild(document__default["default"].createTextNode(titleText));
titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl);
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {
const descriptionEl = document__default["default"].createElement('div');
const descriptionText = item.description || '';
descriptionEl.className = 'vjs-playlist-description';
descriptionEl.appendChild(document__default["default"].createTextNode(descriptionText));
descriptionEl.setAttribute('title', descriptionText);
titleContainerEl.appendChild(descriptionEl);
}
return li;
}
}
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// 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
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs__default["default"].dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
super(player, options);
this.items = [];
if (options.horizontal) {
this.addClass('vjs-playlist-horizontal');
} else {
this.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) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs__default["default"].browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {
this.addClass('vjs-ad-playing');
});
this.on(player, 'adend', () => {
this.removeClass('vjs-ad-playing');
});
this.on('dispose', () => {
this.empty_();
player.playlistMenu = null;
});
this.on(player, 'dispose', () => {
this.dispose();
});
}
createEl() {
return videojs__default["default"].dom.createEl('div', {
className: this.options_.className
});
}
empty_() {
if (this.items && this.items.length) {
this.items.forEach(i => i.dispose());
this.items.length = 0;
}
}
createPlaylist_() {
const playlist = this.player_.playlist() || [];
let list = this.el_.querySelector('.vjs-playlist-item-list');
let overlay = this.el_.querySelector('.vjs-playlist-ad-overlay');
if (!list) {
list = document__default["default"].createElement('ol');
list.className = 'vjs-playlist-item-list';
this.el_.appendChild(list);
}
this.empty_();
// create new items
for (let i = 0; i < playlist.length; i++) {
const item = new PlaylistMenuItem(this.player_, {
item: playlist[i]
}, this.options_);
this.items.push(item);
list.appendChild(item.el_);
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
if (!overlay) {
overlay = document__default["default"].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
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
videojs__default["default"].dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}
}
}
/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const 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 (let 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
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document__default["default"].activeElement !== item.el()) {
videojs__default["default"].dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);
} else if (i === currentItem + 1) {
removeSelectedClass(item);
upNext(item);
} else {
removeSelectedClass(item);
notUpNext(item);
}
}
}
}
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document__default["default"].createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
const Plugin = videojs__default["default"].getPlugin('plugin');
/**
* 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.
*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs__default["default"].obj.merge(defaults, options);
if (!videojs__default["default"].dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
/**
* 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.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs__default["default"].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.
*/
findRoot_(className) {
const all = document__default["default"].querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs__default["default"].registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
module.exports = PlaylistUI;

View file

@ -0,0 +1 @@
.vjs-playlist{padding:0;background-color:#1a1a1a;color:#fff;list-style-type:none}.vjs-playlist img{display:block}.vjs-playlist .vjs-playlist-item-list{position:relative;margin:0;padding:0;list-style:none}.vjs-playlist .vjs-playlist-item{position:relative;cursor:pointer;overflow:hidden}.vjs-playlist .vjs-playlist-thumbnail-placeholder{background:#303030}.vjs-playlist .vjs-playlist-now-playing-text{display:none;position:absolute;top:0;left:0;padding-left:2px;margin:.8rem}.vjs-playlist .vjs-playlist-duration{position:absolute;top:.5rem;left:.5rem;padding:2px 5px 3px;margin-left:2px;background-color:rgba(26,26,26,.8)}.vjs-playlist .vjs-playlist-title-container{position:absolute;bottom:0;box-sizing:border-box;width:100%;padding:.5rem .8rem;text-shadow:1px 1px 2px #000,-1px 1px 2px #000,1px -1px 2px #000,-1px -1px 2px #000}.vjs-playlist .vjs-playlist-name{display:block;max-height:2.5em;padding:0 0 4px 2px;font-style:normal;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;line-height:20px}.vjs-playlist .vjs-playlist-description{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block;font-size:14px;padding:0 0 0 2px}.vjs-playlist .vjs-up-next-text{display:none;padding:.1rem 2px;font-size:.8em;text-transform:uppercase}.vjs-playlist .vjs-up-next .vjs-up-next-text{display:block}.vjs-playlist .vjs-selected{background-color:#141a21}.vjs-playlist .vjs-selected img{opacity:.2}.vjs-playlist .vjs-selected .vjs-playlist-duration{display:none}.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text{display:block}.vjs-playlist .vjs-selected .vjs-playlist-title-container{text-shadow:none}.vjs-playlist-vertical{overflow-x:hidden;overflow-y:auto}.vjs-playlist-vertical img{width:100%;min-height:54px}.vjs-playlist-vertical .vjs-playlist-item{margin-bottom:5px}.vjs-playlist-vertical .vjs-playlist-thumbnail{display:block;width:100%}.vjs-playlist-vertical .vjs-playlist-thumbnail-placeholder{height:100px}.vjs-playlist-horizontal{overflow-x:auto;overflow-y:hidden}.vjs-playlist-horizontal img{min-width:100px;height:100%}.vjs-playlist-horizontal .vjs-playlist-item-list{height:100%;white-space:nowrap}.vjs-playlist-horizontal .vjs-playlist-item{display:inline-block;height:100%;margin-right:5px}.vjs-playlist-horizontal .vjs-playlist-thumbnail{display:block;height:100%}.vjs-playlist-horizontal .vjs-playlist-thumbnail-placeholder{height:100%;width:180px}.vjs-playlist.vjs-ad-playing.vjs-csspointerevents{pointer-events:none;overflow:auto}.vjs-playlist.vjs-ad-playing .vjs-playlist-ad-overlay{display:block;position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.5)}.vjs-playlist{font-size:14px}.vjs-playlist .vjs-playlist-description{height:28px;line-height:21px}.vjs-mouse.vjs-playlist{font-size:15px}.vjs-mouse.vjs-playlist .vjs-playlist-description{height:30px;line-height:23px}@media(min-width: 600px){.vjs-mouse.vjs-playlist{font-size:17px}.vjs-mouse.vjs-playlist .vjs-playlist-description{height:34px;line-height:26px}.vjs-playlist .vjs-playlist-name{line-height:22px}}@media(max-width: 520px){.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,.vjs-playlist .vjs-up-next .vjs-up-next-text{display:none}.vjs-mouse.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,.vjs-mouse.vjs-playlist .vjs-up-next .vjs-up-next-text{display:none}}@media(min-width: 521px){.vjs-playlist img{min-height:85px}}@media(max-width: 750px){.vjs-playlist .vjs-playlist-duration{display:none}}/*# sourceMappingURL=videojs-playlist-ui.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../src/plugin.scss"],"names":[],"mappings":"AAaA,cACE,UACA,iBAdiB,QAejB,MATW,KAUX,qBAEA,kBACE,cAGF,sCACE,kBACA,SACA,UACA,gBAGF,iCACE,kBACA,eACA,gBAGF,kDACE,WA3B2B,QA8B7B,6CACE,aACA,kBACA,MACA,OAGA,iBACA,aAGF,qCACE,kBACA,UACA,WACA,oBACA,gBACA,mCAGF,4CACE,kBACA,SACA,sBACA,WACA,oBACA,oFAMF,iCACE,cACA,iBAGA,oBACA,kBACA,uBACA,gBACA,mBAIA,iBAGF,wCACE,uBACA,gBACA,mBACA,cACA,eACA,kBAGF,gCACE,aACA,kBACA,eACA,yBAKA,6CACE,cAKJ,4BACE,iBA7Gc,QA+Gd,gCACE,WAGF,mDACE,aAGF,2DACE,cAGF,0DACE,iBAMN,uBACE,kBACA,gBAEA,2BACE,WACA,gBAGF,0CACE,kBAGF,+CACE,cACA,WAGF,2DACE,aAKJ,yBACE,gBACA,kBAEA,6BACE,gBACA,YAGF,iDACE,YACA,mBAGF,4CACE,qBACA,YACA,iBAGF,iDACE,cACA,YAGF,6DACE,YACA,YAKJ,kDACE,oBACA,cAIF,sDACE,cACA,kBACA,MACA,OACA,WACA,YACA,gCAcF,cATE,UADoC,KAGpC,wCACE,YACA,iBAUJ,wBAdE,UAeuB,KAbvB,kDACE,YACA,iBAeJ,yBACE,wBApBA,UAqByB,KAnBzB,kDACE,YACA,iBAoBF,iCACE,kBAKJ,yBAGE,wGAEE,aAGF,4HAEE,cASJ,yBACE,kBACE,iBAKJ,yBACE,qCACE","file":"videojs-playlist-ui.css"}

View file

@ -0,0 +1,460 @@
/*! @name videojs-playlist-ui @version 5.0.0 @license Apache-2.0 */
import document from 'global/document';
import videojs from 'video.js';
var version = "5.0.0";
const Component$1 = videojs.getComponent('Component');
const createThumbnail = function (thumbnail) {
if (!thumbnail) {
const placeholder = document.createElement('div');
placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder';
return placeholder;
}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {
// simple thumbnails
const img = document.createElement('img');
img.loading = 'lazy';
img.src = thumbnail;
img.alt = '';
picture.appendChild(img);
} else {
// responsive thumbnails
// additional variations of a <picture> are specified as
// <source> elements
for (let i = 0; i < thumbnail.length - 1; i++) {
const variant = thumbnail[i];
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];
const img = document.createElement('img');
img.loading = 'lazy';
img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {
if (!playlistItem.item) {
throw new Error('Cannot construct a PlaylistMenuItem without an item option');
}
playlistItem.showDescription = settings.showDescription;
super(player, playlistItem);
this.item = playlistItem.item;
this.playOnSelect = settings.playOnSelect;
this.emitTapEvents();
this.on(['click', 'tap'], this.switchPlaylistItem_);
this.on('keydown', this.handleKeyDown_);
}
handleKeyDown_(event) {
// keycode 13 is <Enter>
// keycode 32 is <Space>
if (event.which === 13 || event.which === 32) {
this.switchPlaylistItem_();
}
}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {
this.player_.play();
}
}
createEl() {
const li = document.createElement('li');
const item = this.options_.item;
const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {
const dataKeys = Object.keys(item.data);
dataKeys.forEach(key => {
const 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) {
const duration = document.createElement('time');
const time = videojs.time.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
const nowPlayingEl = document.createElement('span');
const 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"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');
const 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
const titleEl = document.createElement('cite');
const titleText = item.name || this.localize('Untitled Video');
titleEl.className = 'vjs-playlist-name';
titleEl.appendChild(document.createTextNode(titleText));
titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl);
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {
const descriptionEl = document.createElement('div');
const descriptionText = item.description || '';
descriptionEl.className = 'vjs-playlist-description';
descriptionEl.appendChild(document.createTextNode(descriptionText));
descriptionEl.setAttribute('title', descriptionText);
titleContainerEl.appendChild(descriptionEl);
}
return li;
}
}
videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// 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
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs.dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs.getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
super(player, options);
this.items = [];
if (options.horizontal) {
this.addClass('vjs-playlist-horizontal');
} else {
this.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) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs.browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {
this.addClass('vjs-ad-playing');
});
this.on(player, 'adend', () => {
this.removeClass('vjs-ad-playing');
});
this.on('dispose', () => {
this.empty_();
player.playlistMenu = null;
});
this.on(player, 'dispose', () => {
this.dispose();
});
}
createEl() {
return videojs.dom.createEl('div', {
className: this.options_.className
});
}
empty_() {
if (this.items && this.items.length) {
this.items.forEach(i => i.dispose());
this.items.length = 0;
}
}
createPlaylist_() {
const playlist = this.player_.playlist() || [];
let list = this.el_.querySelector('.vjs-playlist-item-list');
let 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 (let i = 0; i < playlist.length; i++) {
const item = new PlaylistMenuItem(this.player_, {
item: playlist[i]
}, this.options_);
this.items.push(item);
list.appendChild(item.el_);
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
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
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
videojs.dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}
}
}
/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const 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 (let 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
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
videojs.dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);
} else if (i === currentItem + 1) {
removeSelectedClass(item);
upNext(item);
} else {
removeSelectedClass(item);
notUpNext(item);
}
}
}
}
videojs.registerComponent('PlaylistMenu', PlaylistMenu);
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
const Plugin = videojs.getPlugin('plugin');
/**
* 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.
*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs.obj.merge(defaults, options);
if (!videojs.dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
/**
* 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.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs.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.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs.registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
export { PlaylistUI as default };

View file

@ -0,0 +1,469 @@
/*! @name videojs-playlist-ui @version 5.0.0 @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 = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsPlaylistUi = factory(global.videojs));
})(this, (function (videojs) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
var version = "5.0.0";
const Component$1 = videojs__default["default"].getComponent('Component');
const createThumbnail = function (thumbnail) {
if (!thumbnail) {
const placeholder = document.createElement('div');
placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder';
return placeholder;
}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {
// simple thumbnails
const img = document.createElement('img');
img.loading = 'lazy';
img.src = thumbnail;
img.alt = '';
picture.appendChild(img);
} else {
// responsive thumbnails
// additional variations of a <picture> are specified as
// <source> elements
for (let i = 0; i < thumbnail.length - 1; i++) {
const variant = thumbnail[i];
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];
const img = document.createElement('img');
img.loading = 'lazy';
img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
class PlaylistMenuItem extends Component$1 {
constructor(player, playlistItem, settings) {
if (!playlistItem.item) {
throw new Error('Cannot construct a PlaylistMenuItem without an item option');
}
playlistItem.showDescription = settings.showDescription;
super(player, playlistItem);
this.item = playlistItem.item;
this.playOnSelect = settings.playOnSelect;
this.emitTapEvents();
this.on(['click', 'tap'], this.switchPlaylistItem_);
this.on('keydown', this.handleKeyDown_);
}
handleKeyDown_(event) {
// keycode 13 is <Enter>
// keycode 32 is <Space>
if (event.which === 13 || event.which === 32) {
this.switchPlaylistItem_();
}
}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {
this.player_.play();
}
}
createEl() {
const li = document.createElement('li');
const item = this.options_.item;
const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {
const dataKeys = Object.keys(item.data);
dataKeys.forEach(key => {
const 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) {
const duration = document.createElement('time');
const time = videojs__default["default"].time.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
const nowPlayingEl = document.createElement('span');
const 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"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');
const 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
const titleEl = document.createElement('cite');
const titleText = item.name || this.localize('Untitled Video');
titleEl.className = 'vjs-playlist-name';
titleEl.appendChild(document.createTextNode(titleText));
titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl);
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {
const descriptionEl = document.createElement('div');
const descriptionText = item.description || '';
descriptionEl.className = 'vjs-playlist-description';
descriptionEl.appendChild(document.createTextNode(descriptionText));
descriptionEl.setAttribute('title', descriptionText);
titleContainerEl.appendChild(descriptionEl);
}
return li;
}
}
videojs__default["default"].registerComponent('PlaylistMenuItem', PlaylistMenuItem);
// 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
const addSelectedClass = function (el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function (el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs__default["default"].dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function (el) {
el.addClass('vjs-up-next');
};
const notUpNext = function (el) {
el.removeClass('vjs-up-next');
};
const Component = videojs__default["default"].getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
super(player, options);
this.items = [];
if (options.horizontal) {
this.addClass('vjs-playlist-horizontal');
} else {
this.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) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs__default["default"].browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], e => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], e => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], e => this.removeItems_(e.index, e.count));
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {
this.addClass('vjs-ad-playing');
});
this.on(player, 'adend', () => {
this.removeClass('vjs-ad-playing');
});
this.on('dispose', () => {
this.empty_();
player.playlistMenu = null;
});
this.on(player, 'dispose', () => {
this.dispose();
});
}
createEl() {
return videojs__default["default"].dom.createEl('div', {
className: this.options_.className
});
}
empty_() {
if (this.items && this.items.length) {
this.items.forEach(i => i.dispose());
this.items.length = 0;
}
}
createPlaylist_() {
const playlist = this.player_.playlist() || [];
let list = this.el_.querySelector('.vjs-playlist-item-list');
let 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 (let i = 0; i < playlist.length; i++) {
const item = new PlaylistMenuItem(this.player_, {
item: playlist[i]
}, this.options_);
this.items.push(item);
list.appendChild(item.el_);
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
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
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
videojs__default["default"].dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}
}
}
/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map(item => {
const menuItem = new PlaylistMenuItem(this.player_, {
item
}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const 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 (let 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
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
videojs__default["default"].dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);
} else if (i === currentItem + 1) {
removeSelectedClass(item);
upNext(item);
} else {
removeSelectedClass(item);
notUpNext(item);
}
}
}
}
videojs__default["default"].registerComponent('PlaylistMenu', PlaylistMenu);
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
const Plugin = videojs__default["default"].getPlugin('plugin');
/**
* 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.
*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs__default["default"].obj.merge(defaults, options);
if (!videojs__default["default"].dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
/**
* 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.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs__default["default"].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.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs__default["default"].registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = version;
return PlaylistUI;
}));

File diff suppressed because one or more lines are too long

18
node_modules/videojs-playlist-ui/index.html generated vendored Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-playlist-ui Demo</title>
</head>
<body>
<ul>
<li><a href="example.html">Example</a></li>
<li><a href="example-two-players.html">Example with Two Playlist Players</a></li>
<li><a href="example-custom-class.html">Example with Custom Class</a></li>
<li><a href="example-custom-element.html">Example with Custom Element</a></li>
<li><a href="example-horizontal.html">Example, Horizontal Implementation</a></li>
<li><a href="example-custom-data.html">Example with Custom Data Attributes</a></li>
<li><a href="test/debug.html">Run unit tests in browser.</a></li>
</ul>
</body>
</html>

105
node_modules/videojs-playlist-ui/package.json generated vendored Normal file
View file

@ -0,0 +1,105 @@
{
"name": "videojs-playlist-ui",
"version": "5.0.0",
"author": "Brightcove, Inc.",
"description": "A user interface for the videojs-playlist API",
"license": "Apache-2.0",
"keywords": [
"playlist",
"videojs",
"videojs-plugin"
],
"scripts": {
"prebuild": "npm run clean",
"build": "npm-run-all -p build:*",
"build:css": "sass src/plugin.scss dist/videojs-playlist-ui.css --style compressed",
"build:js": "rollup -c scripts/rollup.config.js",
"build:lang": "vjslang --dir dist/lang",
"clean": "shx rm -rf ./dist ./test/dist",
"postclean": "shx mkdir -p ./dist ./test/dist",
"docs": "doctoc README.md",
"lint": "vjsstandard",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"start": "npm-run-all -p server watch",
"pretest": "npm-run-all lint build",
"test": "karma start scripts/karma.conf.js",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:css": "npm run build:css -- -w",
"watch:js": "npm run build:js -- -w",
"posttest": "shx cat test/dist/coverage/text.txt",
"prepublishOnly": "npm run build && vjsverify --skip-es-check"
},
"repository": {
"type": "git",
"url": "https://github.com/videojs/videojs-playlist-ui"
},
"dependencies": {
"global": "^4.4.0"
},
"devDependencies": {
"conventional-changelog-cli": "^2.2.2",
"conventional-changelog-videojs": "^3.0.2",
"doctoc": "^2.2.1",
"husky": "^1.3.1",
"karma": "^6.4.2",
"lint-staged": "^13.2.2",
"not-prerelease": "^1.0.1",
"npm-merge-driver-install": "^3.0.0",
"npm-run-all": "^4.1.5",
"pkg-ok": "^2.2.0",
"rollup": "^2.61.1",
"sass": "^1.62.1",
"shx": "^0.3.2",
"sinon": "^6.1.5",
"video.js": "^8.0.0",
"videojs-generate-karma-config": "^8.0.1",
"videojs-generate-rollup-config": "^7.0.0",
"videojs-generator-verify": "^4.0.1",
"videojs-languages": "^1.0.0",
"videojs-playlist": "^5.1.0",
"videojs-standard": "^9.0.1"
},
"peerDependencies": {
"video.js": "^8.0.0",
"videojs-playlist": "^5.1.0"
},
"main": "dist/videojs-playlist-ui.cjs.js",
"module": "dist/videojs-playlist-ui.es.js",
"generator-videojs-plugin": {
"version": "7.3.2"
},
"vjsstandard": {
"jsdoc": false,
"ignore": [
"dist",
"docs",
"test/dist"
]
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"npm run docs",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

13
node_modules/videojs-playlist-ui/scripts/karma.conf.js generated vendored Normal file
View file

@ -0,0 +1,13 @@
const generate = require('videojs-generate-karma-config');
module.exports = function(config) {
// see https://github.com/videojs/videojs-generate-karma-config
// for options
const options = {};
config = generate(config, options);
// any other custom stuff not supported by options here!
};

View file

@ -0,0 +1,9 @@
const generate = require('videojs-generate-postcss-config');
module.exports = function(context) {
const result = generate({}, context);
// do custom stuff here
return result;
};

View file

@ -0,0 +1,11 @@
const generate = require('videojs-generate-rollup-config');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
const options = {};
const config = generate(options);
// Add additonal builds/customization here!
// export the builds to rollup
export default Object.values(config.builds);

View file

@ -0,0 +1,177 @@
import document from 'global/document';
import videojs from 'video.js';
const Component = videojs.getComponent('Component');
const createThumbnail = function(thumbnail) {
if (!thumbnail) {
const placeholder = document.createElement('div');
placeholder.className = 'vjs-playlist-thumbnail vjs-playlist-thumbnail-placeholder';
return placeholder;
}
const picture = document.createElement('picture');
picture.className = 'vjs-playlist-thumbnail';
if (typeof thumbnail === 'string') {
// simple thumbnails
const img = document.createElement('img');
img.loading = 'lazy';
img.src = thumbnail;
img.alt = '';
picture.appendChild(img);
} else {
// responsive thumbnails
// additional variations of a <picture> are specified as
// <source> elements
for (let i = 0; i < thumbnail.length - 1; i++) {
const variant = thumbnail[i];
const source = document.createElement('source');
// transfer the properties of each variant onto a <source>
for (const prop in variant) {
source[prop] = variant[prop];
}
picture.appendChild(source);
}
// the default version of a <picture> is specified by an <img>
const variant = thumbnail[thumbnail.length - 1];
const img = document.createElement('img');
img.loading = 'lazy';
img.alt = '';
for (const prop in variant) {
img[prop] = variant[prop];
}
picture.appendChild(img);
}
return picture;
};
class PlaylistMenuItem extends Component {
constructor(player, playlistItem, settings) {
if (!playlistItem.item) {
throw new Error('Cannot construct a PlaylistMenuItem without an item option');
}
playlistItem.showDescription = settings.showDescription;
super(player, playlistItem);
this.item = playlistItem.item;
this.playOnSelect = settings.playOnSelect;
this.emitTapEvents();
this.on(['click', 'tap'], this.switchPlaylistItem_);
this.on('keydown', this.handleKeyDown_);
}
handleKeyDown_(event) {
// keycode 13 is <Enter>
// keycode 32 is <Space>
if (event.which === 13 || event.which === 32) {
this.switchPlaylistItem_();
}
}
switchPlaylistItem_(event) {
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
if (this.playOnSelect) {
this.player_.play();
}
}
createEl() {
const li = document.createElement('li');
const item = this.options_.item;
const showDescription = this.options_.showDescription;
if (typeof item.data === 'object') {
const dataKeys = Object.keys(item.data);
dataKeys.forEach(key => {
const 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) {
const duration = document.createElement('time');
const time = videojs.time.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
const nowPlayingEl = document.createElement('span');
const 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"
const titleContainerEl = document.createElement('div');
titleContainerEl.className = 'vjs-playlist-title-container';
this.thumbnail.appendChild(titleContainerEl);
// Up next
const upNextEl = document.createElement('span');
const 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
const titleEl = document.createElement('cite');
const titleText = item.name || this.localize('Untitled Video');
titleEl.className = 'vjs-playlist-name';
titleEl.appendChild(document.createTextNode(titleText));
titleEl.setAttribute('title', titleText);
titleContainerEl.appendChild(titleEl);
// Populate thumbnails alt with the video title
this.thumbnail.getElementsByTagName('img').alt = titleText;
// We add thumbnail video description only if specified in playlist options
if (showDescription) {
const descriptionEl = document.createElement('div');
const descriptionText = item.description || '';
descriptionEl.className = 'vjs-playlist-description';
descriptionEl.appendChild(document.createTextNode(descriptionText));
descriptionEl.setAttribute('title', descriptionText);
titleContainerEl.appendChild(descriptionEl);
}
return li;
}
}
videojs.registerComponent('PlaylistMenuItem', PlaylistMenuItem);
export default PlaylistMenuItem;

252
node_modules/videojs-playlist-ui/src/playlist-menu.js generated vendored Normal file
View file

@ -0,0 +1,252 @@
import document from 'global/document';
import videojs from 'video.js';
import PlaylistMenuItem from './playlist-menu-item';
// 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
const addSelectedClass = function(el) {
el.addClass('vjs-selected');
};
const removeSelectedClass = function(el) {
el.removeClass('vjs-selected');
if (el.thumbnail) {
videojs.dom.removeClass(el.thumbnail, 'vjs-playlist-now-playing');
}
};
const upNext = function(el) {
el.addClass('vjs-up-next');
};
const notUpNext = function(el) {
el.removeClass('vjs-up-next');
};
const Component = videojs.getComponent('Component');
class PlaylistMenu extends Component {
constructor(player, options) {
super(player, options);
this.items = [];
if (options.horizontal) {
this.addClass('vjs-playlist-horizontal');
} else {
this.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) {
this.addClass('vjs-csspointerevents');
}
this.createPlaylist_();
if (!videojs.browser.TOUCH_ENABLED) {
this.addClass('vjs-mouse');
}
this.on(player, ['loadstart', 'playlistchange', 'playlistsorted'], (e) => {
// The playlistadd and playlistremove events are handled separately. These
// also fire the playlistchange event with an `action` property, so can
// exclude them here.
if (e.type === 'playlistchange' && ['add', 'remove'].includes(e.action)) {
return;
}
this.update();
});
this.on(player, ['playlistadd'], (e) => this.addItems_(e.index, e.count));
this.on(player, ['playlistremove'], (e) => this.removeItems_(e.index, e.count));
// Keep track of whether an ad is playing so that the menu
// appearance can be adapted appropriately
this.on(player, 'adstart', () => {
this.addClass('vjs-ad-playing');
});
this.on(player, 'adend', () => {
this.removeClass('vjs-ad-playing');
});
this.on('dispose', () => {
this.empty_();
player.playlistMenu = null;
});
this.on(player, 'dispose', () => {
this.dispose();
});
}
createEl() {
return videojs.dom.createEl('div', {className: this.options_.className});
}
empty_() {
if (this.items && this.items.length) {
this.items.forEach(i => i.dispose());
this.items.length = 0;
}
}
createPlaylist_() {
const playlist = this.player_.playlist() || [];
let list = this.el_.querySelector('.vjs-playlist-item-list');
let 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 (let i = 0; i < playlist.length; i++) {
const item = new PlaylistMenuItem(this.player_, {
item: playlist[i]
}, this.options_);
this.items.push(item);
list.appendChild(item.el_);
}
// Inject the ad overlay. We use this element to block clicks during ad
// playback and darken the menu to indicate inactivity
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
const selectedIndex = this.player_.playlist.currentItem();
if (this.items.length && selectedIndex >= 0) {
addSelectedClass(this.items[selectedIndex]);
const thumbnail = this.items[selectedIndex].$('.vjs-playlist-thumbnail');
if (thumbnail) {
videojs.dom.addClass(thumbnail, 'vjs-playlist-now-playing');
}
}
}
/**
* Adds a number of playlist items to the UI.
*
* Each item that was added to the underlying playlist in a certain range
* has a new PlaylistMenuItem created for it.
*
* @param {number} index
* The index at which to start adding items.
*
* @param {number} count
* The number of items to add.
*/
addItems_(index, count) {
const playlist = this.player_.playlist();
const items = playlist.slice(index, count + index);
if (!items.length) {
return;
}
const listEl = this.el_.querySelector('.vjs-playlist-item-list');
const listNodes = this.el_.querySelectorAll('.vjs-playlist-item');
// When appending to the list, `insertBefore` will only reliably accept
// `null` as the second argument, so we need to explicitly fall back to it.
const refNode = listNodes[index] || null;
const menuItems = items.map((item) => {
const menuItem = new PlaylistMenuItem(this.player_, {item}, this.options_);
listEl.insertBefore(menuItem.el_, refNode);
return menuItem;
});
this.items.splice(index, 0, ...menuItems);
}
/**
* Removes a number of playlist items from the UI.
*
* Each PlaylistMenuItem component is disposed properly.
*
* @param {number} index
* The index at which to start removing items.
*
* @param {number} count
* The number of items to remove.
*/
removeItems_(index, count) {
const components = this.items.slice(index, count + index);
if (!components.length) {
return;
}
components.forEach(c => c.dispose());
this.items.splice(index, count);
}
update() {
// replace the playlist items being displayed, if necessary
const 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 (let 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
const currentItem = this.player_.playlist.currentItem();
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (i === currentItem) {
addSelectedClass(item);
if (document.activeElement !== item.el()) {
videojs.dom.addClass(item.thumbnail, 'vjs-playlist-now-playing');
}
notUpNext(item);
} else if (i === currentItem + 1) {
removeSelectedClass(item);
upNext(item);
} else {
removeSelectedClass(item);
notUpNext(item);
}
}
}
}
videojs.registerComponent('PlaylistMenu', PlaylistMenu);
export default PlaylistMenu;

112
node_modules/videojs-playlist-ui/src/plugin.js generated vendored Normal file
View file

@ -0,0 +1,112 @@
import document from 'global/document';
import videojs from 'video.js';
import {version as VERSION} from '../package.json';
import PlaylistMenu from './playlist-menu';
// see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/pointerevents.js
const supportsCssPointerEvents = (() => {
const element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
})();
const defaults = {
className: 'vjs-playlist',
playOnSelect: false,
supportsCssPointerEvents
};
const Plugin = videojs.getPlugin('plugin');
/**
* 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.
*/
class PlaylistUI extends Plugin {
constructor(player, options) {
super(player, options);
if (!player.usingPlugin('playlist')) {
player.log.error('videojs-playlist plugin is required by the videojs-playlist-ui plugin');
return;
}
options = this.options_ = videojs.obj.merge(defaults, options);
if (!videojs.dom.isEl(options.el)) {
options.el = this.findRoot_(options.className);
}
// Expose the playlist menu component on the player as well as the plugin
// This is a bit of an anti-pattern, but it's been that way forever and
// there are likely to be integrations relying on it.
this.playlistMenu = player.playlistMenu = new PlaylistMenu(player, options);
}
/**
* Dispose the plugin.
*/
dispose() {
super.dispose();
this.playlistMenu.dispose();
}
/**
* 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.
*/
hasChildEls_(el) {
for (let i = 0; i < el.childNodes.length; i++) {
if (videojs.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.
*/
findRoot_(className) {
const all = document.querySelectorAll('.' + className);
for (let i = 0; i < all.length; i++) {
if (!this.hasChildEls_(all[i])) {
return all[i];
}
}
}
}
videojs.registerPlugin('playlistUi', PlaylistUI);
PlaylistUI.VERSION = VERSION;
export default PlaylistUI;

269
node_modules/videojs-playlist-ui/src/plugin.scss generated vendored Normal file
View file

@ -0,0 +1,269 @@
// The default color for the playlist menu background, almost black
$background-color: #1a1a1a;
// The color used to emphasize the currently playing video and for effects
$highlight-color: #141a21;
// The primary foreground color
$main-color: #fff;
// Background color for thumbnail placeholders
$placeholder-background-color: #303030;
// Rules common to mouse and touch devices
.vjs-playlist {
padding: 0;
background-color: $background-color;
color: $main-color;
list-style-type: none;
img {
display: block;
}
.vjs-playlist-item-list {
position: relative;
margin: 0;
padding: 0;
list-style: none;
}
.vjs-playlist-item {
position: relative;
cursor: pointer;
overflow: hidden;
}
.vjs-playlist-thumbnail-placeholder {
background: $placeholder-background-color;
}
.vjs-playlist-now-playing-text {
display: none;
position: absolute;
top: 0;
left: 0;
// This keeps this element aligned vertically with vjs-playlist-name.
padding-left: 2px;
margin: .8rem;
}
.vjs-playlist-duration {
position: absolute;
top: .5rem;
left: .5rem;
padding: 2px 5px 3px;
margin-left: 2px;
background-color: rgba(26, 26, 26, 0.8);
}
.vjs-playlist-title-container {
position: absolute;
bottom: 0;
box-sizing: border-box;
width: 100%;
padding: .5rem .8rem;
text-shadow: 1px 1px 2px black,
-1px 1px 2px black,
1px -1px 2px black,
-1px -1px 2px black;
}
.vjs-playlist-name {
display: block;
max-height: 2.5em;
// So drop shadow doesn't get cut off as overflow
padding: 0 0 4px 2px;
font-style: normal;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
// line-height: normal was causing overflow cutoff issues on Android, since normal
// lets different user agents pick a value. Here we set a consistent precise value.
line-height: 20px;
}
.vjs-playlist-description{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: block;
font-size: 14px;
padding: 0 0 0 2px;
}
.vjs-up-next-text {
display: none;
padding: .1rem 2px;
font-size: .8em;
text-transform: uppercase;
}
.vjs-up-next {
.vjs-up-next-text {
display: block;
}
}
// Selected item rules
.vjs-selected {
background-color: $highlight-color;
img {
opacity: .2;
}
.vjs-playlist-duration {
display: none;
}
.vjs-playlist-now-playing-text {
display: block;
}
.vjs-playlist-title-container {
text-shadow: none;
}
}
}
// Vertical/default playlist orientation
.vjs-playlist-vertical {
overflow-x: hidden;
overflow-y: auto;
img {
width: 100%;
min-height: 54px;
}
.vjs-playlist-item {
margin-bottom: 5px;
}
.vjs-playlist-thumbnail {
display: block;
width: 100%;
}
.vjs-playlist-thumbnail-placeholder {
height: 100px;
}
}
// Horizontal playlist orientation
.vjs-playlist-horizontal {
overflow-x: auto;
overflow-y: hidden;
img {
min-width: 100px;
height: 100%;
}
.vjs-playlist-item-list {
height: 100%;
white-space: nowrap;
}
.vjs-playlist-item {
display: inline-block;
height: 100%;
margin-right: 5px;
}
.vjs-playlist-thumbnail {
display: block;
height: 100%;
}
.vjs-playlist-thumbnail-placeholder {
height: 100%;
width: 180px;
}
}
// prevent clicks and scrolling from affecting the playlist during ads
.vjs-playlist.vjs-ad-playing.vjs-csspointerevents {
pointer-events: none;
overflow: auto;
}
// darken the playlist menu display to indicate it's not interactive during ads
.vjs-playlist.vjs-ad-playing .vjs-playlist-ad-overlay {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
// Parametric rules. These are specialized for touch and mouse-based devices
@mixin playlist-base($base-font-size: 14px) {
font-size: $base-font-size;
.vjs-playlist-description {
height: $base-font-size * 2;
line-height: ceil($base-font-size * 1.5);
}
}
// Touch-device playlist dimensions
.vjs-playlist {
@include playlist-base();
}
// Mouse-only playlist dimensions
.vjs-mouse.vjs-playlist {
@include playlist-base(15px);
}
// Larger font size for larger player
@media (min-width: 600px) {
.vjs-mouse.vjs-playlist {
@include playlist-base(17px);
}
.vjs-playlist .vjs-playlist-name {
line-height: 22px;
}
}
// Don't show now playing / up next when there isn't room
@media (max-width: 520px) {
// These styles exist both with and without .vjs-mouse
.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,
.vjs-playlist .vjs-up-next .vjs-up-next-text {
display: none;
}
.vjs-mouse.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,
.vjs-mouse.vjs-playlist .vjs-up-next .vjs-up-next-text {
display: none;
}
}
// If now playing / up next are shown, make sure there is room.
// Only affects thumbnails with very wide aspect ratio, which get
// stretched vertically. We could avoid the stretching by making this a
// CSS background image and using background-size: cover; but then it'd
// get clipped, so not sure that's better.
@media (min-width: 521px) {
.vjs-playlist img {
min-height: 85px;
}
}
// Don't show duration when there isn't room
@media (max-width: 750px) {
.vjs-playlist .vjs-playlist-duration {
display: none;
}
}

6966
node_modules/videojs-playlist-ui/test/dist/bundle.js generated vendored Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

869
node_modules/videojs-playlist-ui/test/plugin.test.js generated vendored Normal file
View file

@ -0,0 +1,869 @@
/* eslint-disable no-console */
import document from 'global/document';
import window from 'global/window';
import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';
import 'videojs-playlist';
import '../src/plugin';
const playlist = [{
name: 'Movie 1',
description: 'Movie 1 description',
duration: 100,
data: {
id: '1',
foo: 'bar'
},
sources: [{
src: '//example.com/movie1.mp4',
type: 'video/mp4'
}]
}, {
sources: [{
src: '//example.com/movie2.mp4',
type: 'video/mp4'
}],
thumbnail: '//example.com/movie2.jpg'
}];
const resolveUrl = url => {
const a = document.createElement('a');
a.href = url;
return a.href;
};
const Html5 = videojs.getTech('Html5');
QUnit.test('the environment is sane', function(assert) {
assert.ok(true, 'everything is swell');
});
function setup() {
this.oldVideojsBrowser = videojs.browser;
videojs.browser = videojs.obj.merge({}, videojs.browser);
this.fixture = document.querySelector('#qunit-fixture');
// force HTML support so the tests run in a reasonable
// environment under phantomjs
this.realIsHtmlSupported = Html5.isSupported;
Html5.isSupported = function() {
return true;
};
// create a video element
const video = document.createElement('video');
this.fixture.appendChild(video);
// create a video.js player
this.player = videojs(video);
// Create two playlist container elements.
this.fixture.appendChild(videojs.dom.createEl('div', {className: 'vjs-playlist'}));
this.fixture.appendChild(videojs.dom.createEl('div', {className: 'vjs-playlist'}));
}
function teardown() {
videojs.browser = this.oldVideojsBrowser;
Html5.isSupported = this.realIsHtmlSupported;
this.player.dispose();
this.player = null;
videojs.dom.emptyEl(this.fixture);
}
QUnit.module('videojs-playlist-ui', {beforeEach: setup, afterEach: teardown});
QUnit.test('registers itself', function(assert) {
assert.ok(this.player.playlistUi, 'registered the plugin');
});
QUnit.test('errors if used without the playlist plugin', function(assert) {
sinon.spy(this.player.log, 'error');
this.player.playlist = null;
this.player.playlistUi();
assert.ok(this.player.log.error.calledOnce, 'player.log.error was called');
});
QUnit.test('is empty if the playlist plugin isn\'t initialized', function(assert) {
this.player.playlistUi();
const items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.ok(this.fixture.querySelector('.vjs-playlist'), 'created the menu');
assert.strictEqual(items.length, 0, 'displayed no items');
});
QUnit.test('can be initialized with an element', function(assert) {
const elem = videojs.dom.createEl('div');
this.player.playlist(playlist);
this.player.playlistUi({el: elem});
assert.strictEqual(
elem.querySelectorAll('li.vjs-playlist-item').length,
playlist.length,
'created an element for each playlist item'
);
});
QUnit.test('can look for an element with the class "vjs-playlist" that is not already in use', function(assert) {
const firstEl = this.fixture.querySelectorAll('.vjs-playlist')[0];
const secondEl = this.fixture.querySelectorAll('.vjs-playlist')[1];
// Give the firstEl a child, so the plugin thinks it is in use and moves on
// to the next one.
firstEl.appendChild(videojs.dom.createEl('div'));
this.player.playlist(playlist);
this.player.playlistUi();
assert.strictEqual(this.player.playlistMenu.el(), secondEl, 'used the first matching/empty element');
assert.strictEqual(
secondEl.querySelectorAll('li.vjs-playlist-item').length,
playlist.length,
'found an element for each playlist item'
);
});
QUnit.test('can look for an element with a custom class that is not already in use', function(assert) {
const firstEl = videojs.dom.createEl('div', {className: 'super-playlist'});
const secondEl = videojs.dom.createEl('div', {className: 'super-playlist'});
// Give the firstEl a child, so the plugin thinks it is in use and moves on
// to the next one.
firstEl.appendChild(videojs.dom.createEl('div'));
this.fixture.appendChild(firstEl);
this.fixture.appendChild(secondEl);
this.player.playlist(playlist);
this.player.playlistUi({
className: 'super-playlist'
});
assert.strictEqual(this.player.playlistMenu.el(), secondEl, 'used the first matching/empty element');
assert.strictEqual(
this.fixture.querySelectorAll('li.vjs-playlist-item').length,
playlist.length,
'created an element for each playlist item'
);
});
QUnit.test('specializes the class name if touch input is absent', function(assert) {
videojs.browser.TOUCH_ENABLED = false;
this.player.playlist(playlist);
this.player.playlistUi();
assert.ok(this.player.playlistMenu.hasClass('vjs-mouse'), 'marked the playlist menu');
});
QUnit.test('can be re-initialized without doubling the contents of the list', function(assert) {
const el = this.fixture.querySelectorAll('.vjs-playlist')[0];
this.player.playlist(playlist);
this.player.playlistUi();
this.player.playlistUi();
this.player.playlistUi();
assert.strictEqual(this.player.playlistMenu.el(), el, 'used the first matching/empty element');
assert.strictEqual(
el.querySelectorAll('li.vjs-playlist-item').length,
playlist.length,
'found an element for each playlist item'
);
});
QUnit.module('videojs-playlist-ui: Components', {beforeEach: setup, afterEach: teardown});
// --------------------
// Creation and Updates
// --------------------
QUnit.test('includes the video name if provided', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(
items[0].querySelector('.vjs-playlist-name').textContent,
playlist[0].name,
'wrote the name'
);
assert.strictEqual(
items[1].querySelector('.vjs-playlist-name').textContent,
'Untitled Video',
'wrote a placeholder for the name'
);
});
QUnit.test('includes the video description if user specifies it', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi({showDescription: true});
const items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(
items[0].querySelector('.vjs-playlist-description').textContent,
playlist[0].description,
'description is displayed'
);
});
QUnit.test('hides video description by default', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(
items[0].querySelector('.vjs-playlist-description'),
null,
'description is not displayed'
);
});
QUnit.test('includes custom data attribute if provided', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(
items[0].dataset.id,
playlist[0].data.id,
'set a single data attribute'
);
assert.strictEqual(
items[0].dataset.id,
'1',
'set a single data attribute (actual value)'
);
assert.strictEqual(
items[0].dataset.foo,
playlist[0].data.foo,
'set an addtional data attribute'
);
assert.strictEqual(
items[0].dataset.foo,
'bar',
'set an addtional data attribute'
);
});
QUnit.test('outputs a <picture> for simple thumbnails', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const pictures = this.fixture.querySelectorAll('.vjs-playlist-item picture');
assert.strictEqual(pictures.length, 1, 'output one picture');
const imgs = pictures[0].querySelectorAll('img');
assert.strictEqual(imgs.length, 1, 'output one img');
assert.strictEqual(imgs[0].src, window.location.protocol + playlist[1].thumbnail, 'set the src attribute');
});
QUnit.test('outputs a <picture> for responsive thumbnails', function(assert) {
const playlistOverride = [{
sources: [{
src: '//example.com/movie.mp4',
type: 'video/mp4'
}],
thumbnail: [{
srcset: '/test/example/oceans.jpg',
type: 'image/jpeg',
media: '(min-width: 400px;)'
}, {
src: '/test/example/oceans-low.jpg'
}]
}];
this.player.playlist(playlistOverride);
this.player.playlistUi();
const sources = this.fixture.querySelectorAll('.vjs-playlist-item picture source');
const imgs = this.fixture.querySelectorAll('.vjs-playlist-item picture img');
assert.strictEqual(sources.length, 1, 'output one source');
assert.strictEqual(
sources[0].srcset,
playlistOverride[0].thumbnail[0].srcset,
'wrote the srcset attribute'
);
assert.strictEqual(
sources[0].type,
playlistOverride[0].thumbnail[0].type,
'wrote the type attribute'
);
assert.strictEqual(
sources[0].media,
playlistOverride[0].thumbnail[0].media,
'wrote the type attribute'
);
assert.strictEqual(imgs.length, 1, 'output one img');
assert.strictEqual(
imgs[0].src,
resolveUrl(playlistOverride[0].thumbnail[1].src),
'output the img src attribute'
);
});
QUnit.test('outputs a placeholder for items without thumbnails', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const thumbnails = this.fixture.querySelectorAll('.vjs-playlist-item .vjs-playlist-thumbnail');
assert.strictEqual(thumbnails.length, playlist.length, 'output two thumbnails');
assert.strictEqual(thumbnails[0].nodeName.toLowerCase(), 'div', 'the second is a placeholder');
});
QUnit.test('includes the duration if one is provided', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
const durations = this.fixture.querySelectorAll('.vjs-playlist-item .vjs-playlist-duration');
assert.strictEqual(durations.length, 1, 'skipped the item without a duration');
assert.strictEqual(
durations[0].textContent,
'1:40',
'wrote the duration'
);
assert.strictEqual(
durations[0].getAttribute('datetime'),
'PT0H0M' + playlist[0].duration + 'S',
'wrote a machine-readable datetime'
);
});
QUnit.test('marks the selected playlist item on startup', function(assert) {
this.player.playlist(playlist);
this.player.currentSrc = () => playlist[0].sources[0].src;
this.player.playlistUi();
const selectedItems = this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected');
assert.strictEqual(selectedItems.length, 1, 'marked one playlist item');
assert.strictEqual(
selectedItems[0].querySelector('.vjs-playlist-name').textContent,
playlist[0].name,
'marked the first playlist item'
);
});
QUnit.test('updates the selected playlist item on loadstart', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
this.player.playlist.currentItem(1);
this.player.currentSrc = () => playlist[1].sources[0].src;
this.player.trigger('loadstart');
const selectedItems = this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected');
assert.strictEqual(
this.fixture.querySelectorAll('.vjs-playlist-item').length,
playlist.length,
'displayed the correct number of items'
);
assert.strictEqual(selectedItems.length, 1, 'marked one playlist item');
assert.strictEqual(
selectedItems[0].querySelector('img').src,
resolveUrl(playlist[1].thumbnail),
'marked the second playlist item'
);
});
QUnit.test('selects no item if the playlist is not in use', function(assert) {
this.player.playlist(playlist);
this.player.playlist.currentItem = () => -1;
this.player.playlistUi();
this.player.trigger('loadstart');
assert.strictEqual(
this.fixture.querySelectorAll('.vjs-playlist-item.vjs-selected').length,
0,
'no items selected'
);
});
QUnit.test('updates on "playlistchange", different lengths', function(assert) {
this.player.playlist([]);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 0, 'no items initially');
this.player.playlist(playlist);
this.player.trigger('playlistchange');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
});
QUnit.test('updates on "playlistchange", equal lengths', function(assert) {
this.player.playlist([{sources: []}, {sources: []}]);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist(playlist);
this.player.trigger('playlistchange');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
assert.strictEqual(this.player.playlistMenu.items[0].item, playlist[0], 'we have updated items');
assert.strictEqual(this.player.playlistMenu.items[1].item, playlist[1], 'we have updated items');
});
QUnit.test('updates on "playlistchange", update selection', function(assert) {
this.player.playlist(playlist);
this.player.currentSrc = function() {
return playlist[0].sources[0].src;
};
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
assert.ok((/vjs-selected/).test(items[0].getAttribute('class')), 'first item is selected by default');
this.player.playlist.currentItem(1);
this.player.currentSrc = function() {
return playlist[1].sources[0].src;
};
this.player.trigger('playlistchange');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
assert.ok((/vjs-selected/).test(items[1].getAttribute('class')), 'second item is selected after update');
assert.ok(!(/vjs-selected/).test(items[0].getAttribute('class')), 'first item is not selected after update');
});
QUnit.test('updates on "playlistsorted", different lengths', function(assert) {
this.player.playlist([]);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 0, 'no items initially');
this.player.playlist(playlist);
this.player.trigger('playlistsorted');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
});
QUnit.test('updates on "playlistsorted", equal lengths', function(assert) {
this.player.playlist([{sources: []}, {sources: []}]);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist(playlist);
this.player.trigger('playlistsorted');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
assert.strictEqual(this.player.playlistMenu.items[0].item, playlist[0], 'we have updated items');
assert.strictEqual(this.player.playlistMenu.items[1].item, playlist[1], 'we have updated items');
});
QUnit.test('updates on "playlistsorted", update selection', function(assert) {
this.player.playlist(playlist);
this.player.currentSrc = function() {
return playlist[0].sources[0].src;
};
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
assert.ok((/vjs-selected/).test(items[0].getAttribute('class')), 'first item is selected by default');
this.player.playlist.currentItem(1);
this.player.currentSrc = function() {
return playlist[1].sources[0].src;
};
this.player.trigger('playlistsorted');
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'updated with the new items');
assert.ok((/vjs-selected/).test(items[1].getAttribute('class')), 'second item is selected after update');
assert.ok(!(/vjs-selected/).test(items[0].getAttribute('class')), 'first item is not selected after update');
});
QUnit.test('tracks when an ad is playing', function(assert) {
this.player.playlist([]);
this.player.playlistUi();
this.player.duration = () => 5;
const playlistMenu = this.player.playlistMenu;
assert.ok(
!playlistMenu.hasClass('vjs-ad-playing'),
'does not have class vjs-ad-playing'
);
this.player.trigger('adstart');
assert.ok(
playlistMenu.hasClass('vjs-ad-playing'),
'has class vjs-ad-playing'
);
this.player.trigger('adend');
assert.ok(
!playlistMenu.hasClass('vjs-ad-playing'),
'does not have class vjs-ad-playing'
);
});
// -----------
// Interaction
// -----------
QUnit.test('changes the selection when tapped', function(assert) {
let playCalled = false;
this.player.playlist(playlist);
this.player.playlistUi({playOnSelect: true});
this.player.play = function() {
playCalled = true;
};
let sources;
this.player.src = (src) => {
if (src) {
sources = src;
}
return sources[0];
};
this.player.currentSrc = () => sources[0].src;
this.player.playlistMenu.items[1].trigger('tap');
// trigger a loadstart synchronously to simplify the test
this.player.trigger('loadstart');
assert.ok(
this.player.playlistMenu.items[1].hasClass('vjs-selected'),
'selected the new item'
);
assert.ok(
!this.player.playlistMenu.items[0].hasClass('vjs-selected'),
'deselected the old item'
);
assert.strictEqual(playCalled, true, 'play gets called if option is set');
});
QUnit.test('play should not get called by default upon selection of menu items ', function(assert) {
let playCalled = false;
this.player.playlist(playlist);
this.player.playlistUi();
this.player.play = function() {
playCalled = true;
};
let sources;
this.player.src = (src) => {
if (src) {
sources = src;
}
return sources[0];
};
this.player.currentSrc = () => sources[0].src;
this.player.playlistMenu.items[1].trigger('tap');
// trigger a loadstart synchronously to simplify the test
this.player.trigger('loadstart');
assert.strictEqual(playCalled, false, 'play should not get called by default');
});
QUnit.test('disposing the playlist menu nulls out the player\'s reference to it', function(assert) {
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');
this.player.playlist(playlist);
this.player.playlistUi();
this.player.playlistMenu.dispose();
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');
});
QUnit.test('disposing the playlist menu removes playlist menu items', function(assert) {
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');
this.player.playlist(playlist);
this.player.playlistUi();
// Cache some references so we can refer to them after disposal.
const items = [].concat(this.player.playlistMenu.items);
this.player.playlistMenu.dispose();
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');
items.forEach(i => {
assert.strictEqual(i.el_, null, `the item "${i.id_}" has been disposed`);
});
});
QUnit.test('disposing the player also disposes the playlist menu', function(assert) {
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 2, 'there are two playlist containers at the start');
this.player.playlist(playlist);
this.player.playlistUi();
this.player.dispose();
assert.strictEqual(this.fixture.querySelectorAll('.vjs-playlist').length, 1, 'only the unused playlist container is left');
assert.strictEqual(this.player.playlistMenu, null, 'the playlistMenu property is null');
});
QUnit.module('videojs-playlist-ui: add/remove', {beforeEach: setup, afterEach: teardown});
QUnit.test('adding zero items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'correct number of items');
});
QUnit.test('adding one item at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('adding one item in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('adding one item at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, playlist.length);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
});
QUnit.test('adding two items at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], playlist.length);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
assert.strictEqual(items[2].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'has the correct name in the playlist DOM');
assert.strictEqual(items[3].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'has the correct name in the playlist DOM');
});
QUnit.test('removing zero items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.remove(0, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, playlist.length, 'correct number of items');
});
QUnit.test('removing one item at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(0, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items at the start of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 0);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(0, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.notStrictEqual(items[0].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'the added item was properly removed from the DOM');
});
QUnit.test('removing one item in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(1, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items in the middle of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(1, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.strictEqual(items[2], undefined, 'the added item was properly removed from the DOM');
});
QUnit.test('removing one item at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add({name: 'Test 1'}, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 3, 'correct number of items');
this.player.playlist.remove(2, 1);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
});
QUnit.test('removing two items at the end of the playlist', function(assert) {
this.player.playlist(playlist);
this.player.playlistUi();
let items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'two items initially');
this.player.playlist.add([{name: 'Test 1'}, {name: 'Test 2'}], 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 4, 'correct number of items');
this.player.playlist.remove(2, 2);
items = this.fixture.querySelectorAll('.vjs-playlist-item');
assert.strictEqual(items.length, 2, 'correct number of items');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 1', 'the added item was properly removed from the DOM');
assert.notStrictEqual(items[1].querySelector('.vjs-playlist-name').textContent, 'Test 2', 'the added item was properly removed from the DOM');
});

209
node_modules/videojs-playlist/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,209 @@
<a name="5.1.0"></a>
# [5.1.0](https://github.com/brightcove/videojs-playlist/compare/v5.0.1...v5.1.0) (2023-04-14)
### Features
* add `add` and `remove` methods to modify the playlist dynamically ([#240](https://github.com/brightcove/videojs-playlist/issues/240)) ([bd4aabb](https://github.com/brightcove/videojs-playlist/commit/bd4aabb))
### Chores
* add v8 to dependencies list ([#250](https://github.com/brightcove/videojs-playlist/issues/250)) ([5c2f9f3](https://github.com/brightcove/videojs-playlist/commit/5c2f9f3))
<a name="5.0.1"></a>
## [5.0.1](https://github.com/brightcove/videojs-playlist/compare/v5.0.0...v5.0.1) (2023-03-15)
### Bug Fixes
* posters flash between videos in playlist ([#243](https://github.com/brightcove/videojs-playlist/issues/243)) ([80dde66](https://github.com/brightcove/videojs-playlist/commit/80dde66))
<a name="5.0.0"></a>
# [5.0.0](https://github.com/brightcove/videojs-playlist/compare/v4.3.1...v5.0.0) (2021-12-17)
### Chores
* skip vjsverify es check ([#199](https://github.com/brightcove/videojs-playlist/issues/199)) ([78ee118](https://github.com/brightcove/videojs-playlist/commit/78ee118))
* Update generate-rollup-config to drop older browser support ([#198](https://github.com/brightcove/videojs-playlist/issues/198)) ([b85db66](https://github.com/brightcove/videojs-playlist/commit/b85db66))
### BREAKING CHANGES
* This removes support for some older browsers like IE 11
<a name="4.3.1"></a>
## [4.3.1](https://github.com/brightcove/videojs-playlist/compare/v4.3.0...v4.3.1) (2019-03-20)
### Bug Fixes
* Fix regression(s) introduced by changes to the currentItem function in 4.3.0 ([#145](https://github.com/brightcove/videojs-playlist/issues/145)) ([d49b929](https://github.com/brightcove/videojs-playlist/commit/d49b929))
### Chores
* **package:** update package-lock.json ([#149](https://github.com/brightcove/videojs-playlist/issues/149)) ([4915847](https://github.com/brightcove/videojs-playlist/commit/4915847))
* **package:** update rollup to version 1.7.0 ([#148](https://github.com/brightcove/videojs-playlist/issues/148)) ([4bf51d4](https://github.com/brightcove/videojs-playlist/commit/4bf51d4))
* **package:** update sinon to version 7.3.0 ([#147](https://github.com/brightcove/videojs-playlist/issues/147)) ([74c3f33](https://github.com/brightcove/videojs-playlist/commit/74c3f33))
<a name="4.3.0"></a>
# [4.3.0](https://github.com/brightcove/videojs-playlist/compare/v4.2.6...v4.3.0) (2019-01-11)
### Features
* Return correct index of a playlist item when there are multiple items with the same source ([#115](https://github.com/brightcove/videojs-playlist/issues/115)) ([0963d58](https://github.com/brightcove/videojs-playlist/commit/0963d58))
### Chores
* **package:** update lint-staged to version 8.1.0 ([#134](https://github.com/brightcove/videojs-playlist/issues/134)) ([7776c14](https://github.com/brightcove/videojs-playlist/commit/7776c14))
* **package:** update npm-run-all/videojs-generator-verify for security ([0491b47](https://github.com/brightcove/videojs-playlist/commit/0491b47))
* **package:** update rollup to version 0.66.0 ([#122](https://github.com/brightcove/videojs-playlist/issues/122)) ([9536367](https://github.com/brightcove/videojs-playlist/commit/9536367))
* **package:** update rollup to version 0.67.3 ([#132](https://github.com/brightcove/videojs-playlist/issues/132)) ([f3f333e](https://github.com/brightcove/videojs-playlist/commit/f3f333e))
* **package:** update videojs-generate-karma-config to version 5.0.0 ([#133](https://github.com/brightcove/videojs-playlist/issues/133)) ([d2953f4](https://github.com/brightcove/videojs-playlist/commit/d2953f4))
* **package:** update videojs-generate-rollup-config to version 2.3.1 ([#135](https://github.com/brightcove/videojs-playlist/issues/135)) ([ab78366](https://github.com/brightcove/videojs-playlist/commit/ab78366))
<a name="4.2.6"></a>
## [4.2.6](https://github.com/brightcove/videojs-playlist/compare/v4.2.5...v4.2.6) (2018-09-05)
### Bug Fixes
* Remove the postinstall script to prevent install issues ([#119](https://github.com/brightcove/videojs-playlist/issues/119)) ([159fafe](https://github.com/brightcove/videojs-playlist/commit/159fafe))
<a name="4.2.5"></a>
## [4.2.5](https://github.com/brightcove/videojs-playlist/compare/v4.2.4...v4.2.5) (2018-08-30)
### Chores
* update generator to v7.1.1 ([12c5d53](https://github.com/brightcove/videojs-playlist/commit/12c5d53))
* **package:** Update rollup to version 0.65.0 ([#116](https://github.com/brightcove/videojs-playlist/issues/116)) ([17d6a37](https://github.com/brightcove/videojs-playlist/commit/17d6a37))
* update to generator-videojs-plugin[@7](https://github.com/7).2.0 ([4b90483](https://github.com/brightcove/videojs-playlist/commit/4b90483))
<a name="4.2.4"></a>
## [4.2.4](https://github.com/brightcove/videojs-playlist/compare/v4.2.3...v4.2.4) (2018-08-23)
### Chores
* generator v7 ([#114](https://github.com/brightcove/videojs-playlist/issues/114)) ([e671236](https://github.com/brightcove/videojs-playlist/commit/e671236))
<a name="4.2.3"></a>
## [4.2.3](https://github.com/brightcove/videojs-playlist/compare/v4.2.2...v4.2.3) (2018-08-03)
### Bug Fixes
* babel the es dist, by updating the generator ([#107](https://github.com/brightcove/videojs-playlist/issues/107)) ([4f1fdb9](https://github.com/brightcove/videojs-playlist/commit/4f1fdb9))
### Chores
* **package:** update dependencies, enable greenkeeper ([#106](https://github.com/brightcove/videojs-playlist/issues/106)) ([5ed060e](https://github.com/brightcove/videojs-playlist/commit/5ed060e))
<a name="4.2.2"></a>
## [4.2.2](https://github.com/brightcove/videojs-playlist/compare/v4.2.1...v4.2.2) (2018-07-05)
### Chores
* generator v6 ([#102](https://github.com/brightcove/videojs-playlist/issues/102)) ([8c50798](https://github.com/brightcove/videojs-playlist/commit/8c50798))
<a name="4.2.1"></a>
## [4.2.1](https://github.com/brightcove/videojs-playlist/compare/v4.2.0...v4.2.1) (2018-06-13)
### Features
* Expose the version of the plugin at the `VERSION` property. ([#94](https://github.com/brightcove/videojs-playlist/issues/94)) ([d71dec1](https://github.com/brightcove/videojs-playlist/commit/d71dec1))
<a name="4.2.0"></a>
# [4.2.0](https://github.com/brightcove/videojs-playlist/compare/v4.1.1...v4.2.0) (2018-01-25)
### Features
* Add 'duringplaylistchange' event. ([#92](https://github.com/brightcove/videojs-playlist/issues/92)) ([eb80503](https://github.com/brightcove/videojs-playlist/commit/eb80503))
* Add 'rest' option to the shuffle method. ([#91](https://github.com/brightcove/videojs-playlist/issues/91)) ([57d5f0c](https://github.com/brightcove/videojs-playlist/commit/57d5f0c))
<a name="4.1.1"></a>
## [4.1.1](https://github.com/brightcove/videojs-playlist/compare/v4.1.0...v4.1.1) (2018-01-08)
### Bug Fixes
* Fix an issue where we could auto-advance even if the user restarted playback after an ended event. ([#88](https://github.com/brightcove/videojs-playlist/issues/88)) ([5d872d1](https://github.com/brightcove/videojs-playlist/commit/5d872d1))
<a name="4.1.0"></a>
# [4.1.0](https://github.com/brightcove/videojs-playlist/compare/v4.0.2...v4.1.0) (2017-11-28)
### Features
* Add new methods: `currentIndex`, `nextIndex`, `previousIndex`, `lastIndex`, `sort`, `reverse`, and `shuffle`. ([#87](https://github.com/brightcove/videojs-playlist/issues/87)) ([271a27b](https://github.com/brightcove/videojs-playlist/commit/271a27b))
### Documentation
* Fix missing call to playlist ([#86](https://github.com/brightcove/videojs-playlist/issues/86)) ([a7ffd57](https://github.com/brightcove/videojs-playlist/commit/a7ffd57))
<a name="4.0.2"></a>
## [4.0.2](https://github.com/brightcove/videojs-playlist/compare/v4.0.1...v4.0.2) (2017-11-13)
### Bug Fixes
* Fix item switching for asynchronous source setting in Video.js 6. ([#85](https://github.com/brightcove/videojs-playlist/issues/85)) ([8a77bf0](https://github.com/brightcove/videojs-playlist/commit/8a77bf0))
<a name="4.0.1"></a>
## [4.0.1](https://github.com/brightcove/videojs-playlist/compare/v4.0.0...v4.0.1) (2017-10-16)
### Chores
* depend on either vjs 5.x or 6.x ([#84](https://github.com/brightcove/videojs-playlist/issues/84)) ([3f3c946](https://github.com/brightcove/videojs-playlist/commit/3f3c946))
<a name="4.0.0"></a>
# [4.0.0](https://github.com/brightcove/videojs-playlist/compare/v2.0.0...v4.0.0) (2017-05-19)
### Chores
* Update tooling using generator v5 prerelease. ([#79](https://github.com/brightcove/videojs-playlist/issues/79)) ([0b53140](https://github.com/brightcove/videojs-playlist/commit/0b53140))
### BREAKING CHANGES
* Remove Bower support.
## 3.1.1 (2017-04-27)
_(none)_
## 3.1.0 (2017-04-03)
* @incompl Add repeat functionality to plugin [#71](https://github.com/brightcove/videojs-playlist/pull/71)
## 3.0.2 (2017-02-10)
* @misteroneill Suppress videojs.plugin deprecation warning in Video.js 6 [#68](https://github.com/brightcove/videojs-playlist/pull/68)
## 3.0.1 (2017-01-30)
* @misteroneill Update project to use latest version of plugin generator as well as ensure cross-version support between Video.js 5 and 6. [#63](https://github.com/brightcove/videojs-playlist/pull/63)
## 3.0.0 (2016-09-12)
* @misteroneill Remove Brightcove VideoCloud-specific Code [#51](https://github.com/brightcove/videojs-playlist/pull/51)
## 2.5.0 (2016-09-12)
* @mister-ben Load playlist with initial video at specified index or no starting video [#38](https://github.com/brightcove/videojs-playlist/pull/38)
## 2.4.1 (2016-04-21)
* @gkatsev fixed build scripts to only apply browserify-shim for dist files. Fixes [#36](https://github.com/brightcove/videojs-playlist/issues/36). [#44](https://github.com/brightcove/videojs-playlist/pull/44)
## 2.4.0 (2016-04-21)
* @vdeshpande Fixed an issue where incorrect end time value was used [#43](https://github.com/brightcove/videojs-playlist/pull/43)
## 2.3.0 (2016-04-19)
* @vdeshpande Support cue point intervals [#37](https://github.com/brightcove/videojs-playlist/pull/37)
## 2.2.0 (2016-01-29)
* @forbesjo Support turning a list of cue points into a TextTrack [#24](https://github.com/brightcove/videojs-playlist/pull/24)
## 2.1.0 (2015-12-30)
* @misteroneill Moved to the generator-videojs-plugin format and added `last()` method [#23](https://github.com/brightcove/videojs-playlist/pull/23)
## 2.0.0 (2015-11-25)
* @misteroneill Updates for video.js 5.x [#22](https://github.com/brightcove/videojs-playlist/pull/22)
* @saramartinez Fix typos in examples for `currentItem()` method [#18](https://github.com/brightcove/videojs-playlist/pull/18)
## 1.0.3 (2015-08-24)
* @forbesjo README update [#16](https://github.com/brightcove/videojs-playlist/pull/16)
* @forbesjo Fix for playlist items without a `src` [#14](https://github.com/brightcove/videojs-playlist/pull/14)
## 1.0.2 (2015-04-09)
* @gkatsev Explicitly define which files are included.
## 1.0.1 (2015-03-30)
* @gkatsev Added missing repository field to `package.json`.
## 1.0.0 (2015-03-30)
* @gkatsev Initial release.

30
node_modules/videojs-playlist/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,30 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have Node.js 4.8 or higher and npm installed.
1. Fork this repository and clone your fork
1. Install dependencies: `npm install`
1. Run a development server: `npm start`
### Making Changes
Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
### Running Tests
Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
- In all available and supported browsers: `npm test`
- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
[karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md

13
node_modules/videojs-playlist/LICENSE generated vendored Normal file
View file

@ -0,0 +1,13 @@
Copyright Brightcove, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

96
node_modules/videojs-playlist/README.md generated vendored Normal file
View file

@ -0,0 +1,96 @@
# videojs-playlist
[![Build Status](https://travis-ci.org/brightcove/videojs-playlist.svg?branch=master)](https://travis-ci.org/brightcove/videojs-playlist)
[![Greenkeeper badge](https://badges.greenkeeper.io/brightcove/videojs-playlist.svg)](https://greenkeeper.io/)
[![Slack Status](http://slack.videojs.com/badge.svg)](http://slack.videojs.com)
[![NPM](https://nodei.co/npm/videojs-playlist.png?downloads=true&downloadRank=true)](https://nodei.co/npm/videojs-playlist/)
A plugin to enable playlists in video.js
Maintenance Status: Stable
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Inclusion](#inclusion)
- [Basic Usage](#basic-usage)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
Install videojs-playlist via npm (preferred):
```sh
$ npm install videojs-playlist
```
Or Bower:
```sh
$ bower install videojs-playlist
```
## Inclusion
Include videojs-playlist on your website using the tool(s) of your choice.
The simplest method of inclusion is a `<script>` tag after the video.js `<script>` tag:
```html
<script src="path/to/video.js/dist/video.js"></script>
<script src="path/to/videojs-playlist/dist/videojs-playlist.js"></script>
```
When installed via npm, videojs-playlist supports Browserify-based workflows out of the box.
## Basic Usage
For full details on how to use the playlist plugin can be found in [the API documentation](docs/api.md).
```js
var player = videojs('video');
player.playlist([{
sources: [{
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: 'http://media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/bunny/poster.png'
}, {
sources: [{
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
}],
poster: 'http://www.videojs.com/img/poster.jpg'
}, {
sources: [{
src: 'http://media.w3.org/2010/05/bunny/movie.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/bunny/poster.png'
}, {
sources: [{
src: 'http://media.w3.org/2010/05/video/movie_300.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/video/poster.png'
}]);
// Play through the playlist automatically.
player.playlist.autoadvance(0);
```
## License
Apache-2.0. Copyright (c) Brightcove, Inc.

View file

@ -0,0 +1,988 @@
/*! @name videojs-playlist @version 5.1.0 @license Apache-2.0 */
'use strict';
var videojs = require('video.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
/**
* 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
*/
const validSeconds = s => 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
*/
let reset = player => {
const aa = player.playlist.autoadvance_;
if (aa.timeout) {
player.clearTimeout(aa.timeout);
}
if (aa.trigger) {
player.off('ended', aa.trigger);
}
aa.timeout = null;
aa.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
*/
const setup = (player, delay) => {
reset(player); // Before queuing up new auto-advance behavior, check if `seconds` was
// called with a valid value.
if (!validSeconds(delay)) {
player.playlist.autoadvance_.delay = null;
return;
}
player.playlist.autoadvance_.delay = delay;
player.playlist.autoadvance_.trigger = function () {
// This calls setup again, which will reset the existing auto-advance and
// set up another auto-advance for the next "ended" event.
const cancelOnPlay = () => setup(player, delay); // If there is a "play" event while we're waiting for an auto-advance,
// we need to cancel the auto-advance. This could mean the user seeked
// back into the content or restarted the content. This is reproducible
// with an auto-advance > 0.
player.one('play', cancelOnPlay);
player.playlist.autoadvance_.timeout = player.setTimeout(() => {
reset(player);
player.off('play', cancelOnPlay);
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
*/
const clearTracks = player => {
const tracks = player.remoteTextTracks();
let 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 {Object} item
* A source from the playlist.
*
* @return {Player}
* The player that is now playing the item
*/
const playItem = (player, item) => {
const replay = !player.paused() || player.ended();
player.trigger('beforeplaylistitem', item.originalValue || item);
if (item.playlistItemId_) {
player.playlist.currentPlaylistItemId_ = item.playlistItemId_;
}
player.poster(item.poster || '');
player.src(item.sources);
clearTracks(player);
player.ready(() => {
(item.textTracks || []).forEach(player.addRemoteTextTrack.bind(player));
player.trigger('playlistitem', item.originalValue || item);
if (replay) {
const playPromise = player.play(); // silence error when a pause interrupts a play request
// on browsers which return a promise
if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
playPromise.then(null, e => {});
}
}
setup(player, player.playlist.autoadvance_.delay);
});
return player;
};
let guid = 1;
/**
* Transform any primitive playlist item value into an object.
*
* For non-object values, adds a property to the transformed item containing
* original value passed.
*
* For all items, add a unique ID to each playlist item object. This id is
* used to determine the index of an item in the playlist array in cases where
* there are multiple otherwise identical items.
*
* @param {Object} newItem
* An playlist item object, but accepts any value.
*
* @return {Object}
*/
const preparePlaylistItem = newItem => {
let item = newItem;
if (!newItem || typeof newItem !== 'object') {
// Casting to an Object in this way allows primitives to retain their
// primitiveness (i.e. they will be cast back to primitives as needed).
item = Object(newItem);
item.originalValue = newItem;
}
item.playlistItemId_ = guid++;
return item;
};
/**
* Look through an array of playlist items and passes them to
* preparePlaylistItem.
*
* @private
*
* @param {Array} arr
* An array of playlist items
*
* @return {Array}
* A new array with transformed items
*/
const preparePlaylistItems = arr => arr.map(preparePlaylistItem);
/**
* Look through an array of playlist items for a specific playlist item id.
*
* @private
* @param {Array} list
* An array of playlist items to look through
*
* @param {number} currentItemId
* The current item ID.
*
* @return {number}
* The index of the playlist item or -1 if not found
*/
const indexInPlaylistItemIds = (list, currentItemId) => {
for (let i = 0; i < list.length; i++) {
if (list[i].playlistItemId_ === currentItemId) {
return i;
}
}
return -1;
};
/**
* 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
*/
const sourceEquals = (source1, source2) => {
let src1 = source1;
let src2 = source2;
if (typeof source1 === 'object') {
src1 = source1.src;
}
if (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
*/
const indexInSources = (arr, src) => {
for (let i = 0; i < arr.length; i++) {
const sources = arr[i].sources;
if (Array.isArray(sources)) {
for (let j = 0; j < sources.length; j++) {
const source = sources[j];
if (source && sourceEquals(source, src)) {
return i;
}
}
}
}
return -1;
};
/**
* Randomize the contents of an array.
*
* @private
* @param {Array} arr
* An array.
*
* @return {Array}
* The same array that was passed in.
*/
const randomize = arr => {
let index = -1;
const lastIndex = arr.length - 1;
while (++index < arr.length) {
const rand = index + Math.floor(Math.random() * (lastIndex - index + 1));
const value = arr[rand];
arr[rand] = arr[index];
arr[index] = value;
}
return arr;
};
/**
* 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.
*/
function factory(player, initialList, initialIndex = 0) {
let list = null;
let changing = false;
/**
* 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
*/
const playlist = player.playlist = (nextPlaylist, newIndex = 0) => {
if (changing) {
throw new Error('do not call playlist() during a playlist change');
}
if (Array.isArray(nextPlaylist)) {
// @todo - Simplify this to `list.slice()` for v5.
const previousPlaylist = Array.isArray(list) ? list.slice() : null;
list = preparePlaylistItems(nextPlaylist); // Mark the playlist as changing during the duringplaylistchange lifecycle.
changing = true;
player.trigger({
type: 'duringplaylistchange',
nextIndex: newIndex,
nextPlaylist,
previousIndex: playlist.currentIndex_,
// @todo - Simplify this to simply pass along `previousPlaylist` for v5.
previousPlaylist: previousPlaylist || []
});
changing = false;
if (newIndex !== -1) {
playlist.currentItem(newIndex);
} // The only time the previous playlist is null is the first call to this
// function. This allows us to fire the `duringplaylistchange` event
// every time the playlist is populated and to maintain backward
// compatibility by not firing the `playlistchange` event on the initial
// population of the list.
//
// @todo - Remove this condition in preparation for v5.
if (previousPlaylist) {
player.setTimeout(() => {
player.trigger({
type: 'playlistchange',
action: 'change'
});
}, 0);
}
} // Always return a shallow clone of the playlist list.
// We also want to return originalValue if any item in the list has it.
return list.map(item => item.originalValue || item);
}; // On a new source, if there is no current item, disable auto-advance.
player.on('loadstart', () => {
if (playlist.currentItem() === -1) {
reset(player);
}
});
playlist.currentIndex_ = -1;
playlist.player_ = player;
playlist.autoadvance_ = {};
playlist.repeat_ = false;
playlist.currentPlaylistItemId_ = null;
/**
* Get or set the current item in the playlist.
*
* During the duringplaylistchange event, acts only as a getter.
*
* @param {number} [index]
* If given as a valid value, plays the playlist item at that index.
*
* @return {number}
* The current item index.
*/
playlist.currentItem = index => {
// If the playlist is changing, only act as a getter.
if (changing) {
return playlist.currentIndex_;
} // Act as a setter when the index is given and is a valid number.
if (typeof index === 'number' && playlist.currentIndex_ !== index && index >= 0 && index < list.length) {
playlist.currentIndex_ = index;
playItem(playlist.player_, list[playlist.currentIndex_]); // When playing multiple videos in a playlist the videojs PosterImage
// will be hidden using CSS. However, in some browsers the native poster
// attribute will briefly appear while the new source loads. Prevent
// this by hiding every poster after the first play list item. This
// doesn't cover every use case for showing/hiding the poster, but
// it will significantly improve the user experience.
if (index > 0) {
player.poster('');
}
return playlist.currentIndex_;
}
const src = playlist.player_.currentSrc() || ''; // If there is a currentPlaylistItemId_, validate that it matches the
// current source URL returned by the player. This is sufficient evidence
// to suggest that the source was set by the playlist plugin. This code
// exists primarily to deal with playlists where multiple items have the
// same source.
if (playlist.currentPlaylistItemId_) {
const indexInItemIds = indexInPlaylistItemIds(list, playlist.currentPlaylistItemId_);
const item = list[indexInItemIds]; // Found a match, this is our current index!
if (item && Array.isArray(item.sources) && indexInSources([item], src) > -1) {
playlist.currentIndex_ = indexInItemIds;
return playlist.currentIndex_;
} // If this does not match the current source, null it out so subsequent
// calls can skip this step.
playlist.currentPlaylistItemId_ = null;
} // Finally, if we don't have a valid, current playlist item ID, we can
// auto-detect it based on the player's current source URL.
playlist.currentIndex_ = playlist.indexOf(src);
return playlist.currentIndex_;
};
/**
* A custom DOM event that is fired when new item(s) are added to the current
* playlist (rather than replacing the entire playlist).
*
* Unlike playlistchange, this is fired synchronously as it does not
* affect playback.
*
* @typedef {Object} PlaylistAddEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistadd"
*
* @property {number} count
* The number of items that were added.
*
* @property {number} index
* The starting index where item(s) were added.
*/
/**
* A custom DOM event that is fired when new item(s) are removed from the
* current playlist (rather than replacing the entire playlist).
*
* This is fired synchronously as it does not affect playback.
*
* @typedef {Object} PlaylistRemoveEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistremove"
*
* @property {number} count
* The number of items that were removed.
*
* @property {number} index
* The starting index where item(s) were removed.
*/
/**
* Add one or more items to the playlist.
*
* @fires {PlaylistAddEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {string|Object|Array} item
* An item - or array of items - to be added to the playlist.
*
* @param {number} [index]
* If given as a valid value, injects the new playlist item(s)
* starting from that index. Otherwise, the item(s) are appended.
*/
playlist.add = (items, index) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
index = list.length;
}
if (!Array.isArray(items)) {
items = [items];
}
list.splice(index, 0, ...preparePlaylistItems(items)); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'add'
});
player.trigger({
type: 'playlistadd',
count: items.length,
index
});
};
/**
* Remove one or more items from the playlist.
*
* @fires {PlaylistRemoveEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {number} index
* If a valid index in the current playlist, removes the item at that
* index from the playlist.
*
* If no valid index is given, nothing is removed from the playlist.
*
* @param {number} [count=1]
* The number of items to remove from the playlist.
*/
playlist.remove = (index, count = 1) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
return;
}
list.splice(index, count); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'remove'
});
player.trigger({
type: 'playlistremove',
count,
index
});
};
/**
* Checks if the playlist contains a value.
*
* @param {string|Object|Array} value
* The value to check
*
* @return {boolean}
* The result
*/
playlist.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
*/
playlist.indexOf = value => {
if (typeof value === 'string') {
return indexInSources(list, value);
}
const sources = Array.isArray(value) ? value : value.sources;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (typeof source === 'string') {
return indexInSources(list, source);
} else if (source.src) {
return indexInSources(list, source.src);
}
}
return -1;
};
/**
* Get the index of the current item in the playlist. This is identical to
* calling `currentItem()` with no arguments.
*
* @return {number}
* The current item index.
*/
playlist.currentIndex = () => playlist.currentItem();
/**
* Get the index of the last item in the playlist.
*
* @return {number}
* The index of the last item in the playlist or -1 if there are no
* items.
*/
playlist.lastIndex = () => list.length - 1;
/**
* Get the index of the next item in the playlist.
*
* @return {number}
* The index of the next item in the playlist or -1 if there is no
* current item.
*/
playlist.nextIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
}
const lastIndex = playlist.lastIndex(); // When repeating, loop back to the beginning on the last item.
if (playlist.repeat_ && current === lastIndex) {
return 0;
} // Don't go past the end of the playlist.
return Math.min(current + 1, lastIndex);
};
/**
* Get the index of the previous item in the playlist.
*
* @return {number}
* The index of the previous item in the playlist or -1 if there is
* no current item.
*/
playlist.previousIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
} // When repeating, loop back to the end of the playlist.
if (playlist.repeat_ && current === 0) {
return playlist.lastIndex();
} // Don't go past the beginning of the playlist.
return Math.max(current - 1, 0);
};
/**
* Plays the first item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if the list is empty.
*/
playlist.first = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(0);
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
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.
*/
playlist.last = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(playlist.lastIndex());
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
playlist.currentIndex_ = -1;
};
/**
* Plays the next item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on last item.
*/
playlist.next = () => {
if (changing) {
return;
}
const index = playlist.nextIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Plays the previous item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on first item.
*/
playlist.previous = () => {
if (changing) {
return;
}
const index = playlist.previousIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Set up auto-advance on the playlist.
*
* @param {number} [delay]
* The number of seconds to wait before each auto-advance.
*/
playlist.autoadvance = 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
*/
playlist.repeat = val => {
if (val === undefined) {
return playlist.repeat_;
}
if (typeof val !== 'boolean') {
videojs__default["default"].log.error('videojs-playlist: Invalid value for repeat', val);
return;
}
playlist.repeat_ = !!val;
return playlist.repeat_;
};
/**
* Sorts the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
* @fires playlistsorted
*
* @param {Function} compare
* A comparator function as per the native Array method.
*/
playlist.sort = compare => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.sort(compare); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Reverses the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse}
* @fires playlistsorted
*/
playlist.reverse = () => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.reverse(); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Shuffle the contents of the list randomly.
*
* @see {@link https://github.com/lodash/lodash/blob/40e096b6d5291a025e365a0f4c010d9a0efb9a69/shuffle.js}
* @fires playlistsorted
* @todo Make the `rest` option default to `true` in v5.0.0.
* @param {Object} [options]
* An object containing shuffle options.
*
* @param {boolean} [options.rest = false]
* By default, the entire playlist is randomized. However, this may
* not be desirable in all cases, such as when a user is already
* watching a video.
*
* When `true` is passed for this option, it will only shuffle
* playlist items after the current item. For example, when on the
* first item, will shuffle the second item and beyond.
*/
playlist.shuffle = ({
rest
} = {}) => {
let index = 0;
let arr = list; // When options.rest is true, start randomization at the item after the
// current item.
if (rest) {
index = playlist.currentIndex_ + 1;
arr = list.slice(index);
} // Bail if the array is empty or too short to shuffle.
if (arr.length <= 1) {
return;
}
randomize(arr); // When options.rest is true, splice the randomized sub-array back into
// the original array.
if (rest) {
list.splice(...[index, arr.length].concat(arr));
} // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
}; // If an initial list was given, populate the playlist with it.
if (Array.isArray(initialList)) {
playlist(initialList, initialIndex); // If there is no initial list given, silently set an empty array.
} else {
list = [];
}
return playlist;
}
var version = "5.1.0";
const registerPlugin = videojs__default["default"].registerPlugin || videojs__default["default"].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
*/
const plugin = function (list, item) {
factory(this, list, item);
};
registerPlugin('playlist', plugin);
plugin.VERSION = version;
module.exports = plugin;

View file

@ -0,0 +1,982 @@
/*! @name videojs-playlist @version 5.1.0 @license Apache-2.0 */
import videojs from 'video.js';
/**
* 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
*/
const validSeconds = s => 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
*/
let reset = player => {
const aa = player.playlist.autoadvance_;
if (aa.timeout) {
player.clearTimeout(aa.timeout);
}
if (aa.trigger) {
player.off('ended', aa.trigger);
}
aa.timeout = null;
aa.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
*/
const setup = (player, delay) => {
reset(player); // Before queuing up new auto-advance behavior, check if `seconds` was
// called with a valid value.
if (!validSeconds(delay)) {
player.playlist.autoadvance_.delay = null;
return;
}
player.playlist.autoadvance_.delay = delay;
player.playlist.autoadvance_.trigger = function () {
// This calls setup again, which will reset the existing auto-advance and
// set up another auto-advance for the next "ended" event.
const cancelOnPlay = () => setup(player, delay); // If there is a "play" event while we're waiting for an auto-advance,
// we need to cancel the auto-advance. This could mean the user seeked
// back into the content or restarted the content. This is reproducible
// with an auto-advance > 0.
player.one('play', cancelOnPlay);
player.playlist.autoadvance_.timeout = player.setTimeout(() => {
reset(player);
player.off('play', cancelOnPlay);
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
*/
const clearTracks = player => {
const tracks = player.remoteTextTracks();
let 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 {Object} item
* A source from the playlist.
*
* @return {Player}
* The player that is now playing the item
*/
const playItem = (player, item) => {
const replay = !player.paused() || player.ended();
player.trigger('beforeplaylistitem', item.originalValue || item);
if (item.playlistItemId_) {
player.playlist.currentPlaylistItemId_ = item.playlistItemId_;
}
player.poster(item.poster || '');
player.src(item.sources);
clearTracks(player);
player.ready(() => {
(item.textTracks || []).forEach(player.addRemoteTextTrack.bind(player));
player.trigger('playlistitem', item.originalValue || item);
if (replay) {
const playPromise = player.play(); // silence error when a pause interrupts a play request
// on browsers which return a promise
if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
playPromise.then(null, e => {});
}
}
setup(player, player.playlist.autoadvance_.delay);
});
return player;
};
let guid = 1;
/**
* Transform any primitive playlist item value into an object.
*
* For non-object values, adds a property to the transformed item containing
* original value passed.
*
* For all items, add a unique ID to each playlist item object. This id is
* used to determine the index of an item in the playlist array in cases where
* there are multiple otherwise identical items.
*
* @param {Object} newItem
* An playlist item object, but accepts any value.
*
* @return {Object}
*/
const preparePlaylistItem = newItem => {
let item = newItem;
if (!newItem || typeof newItem !== 'object') {
// Casting to an Object in this way allows primitives to retain their
// primitiveness (i.e. they will be cast back to primitives as needed).
item = Object(newItem);
item.originalValue = newItem;
}
item.playlistItemId_ = guid++;
return item;
};
/**
* Look through an array of playlist items and passes them to
* preparePlaylistItem.
*
* @private
*
* @param {Array} arr
* An array of playlist items
*
* @return {Array}
* A new array with transformed items
*/
const preparePlaylistItems = arr => arr.map(preparePlaylistItem);
/**
* Look through an array of playlist items for a specific playlist item id.
*
* @private
* @param {Array} list
* An array of playlist items to look through
*
* @param {number} currentItemId
* The current item ID.
*
* @return {number}
* The index of the playlist item or -1 if not found
*/
const indexInPlaylistItemIds = (list, currentItemId) => {
for (let i = 0; i < list.length; i++) {
if (list[i].playlistItemId_ === currentItemId) {
return i;
}
}
return -1;
};
/**
* 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
*/
const sourceEquals = (source1, source2) => {
let src1 = source1;
let src2 = source2;
if (typeof source1 === 'object') {
src1 = source1.src;
}
if (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
*/
const indexInSources = (arr, src) => {
for (let i = 0; i < arr.length; i++) {
const sources = arr[i].sources;
if (Array.isArray(sources)) {
for (let j = 0; j < sources.length; j++) {
const source = sources[j];
if (source && sourceEquals(source, src)) {
return i;
}
}
}
}
return -1;
};
/**
* Randomize the contents of an array.
*
* @private
* @param {Array} arr
* An array.
*
* @return {Array}
* The same array that was passed in.
*/
const randomize = arr => {
let index = -1;
const lastIndex = arr.length - 1;
while (++index < arr.length) {
const rand = index + Math.floor(Math.random() * (lastIndex - index + 1));
const value = arr[rand];
arr[rand] = arr[index];
arr[index] = value;
}
return arr;
};
/**
* 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.
*/
function factory(player, initialList, initialIndex = 0) {
let list = null;
let changing = false;
/**
* 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
*/
const playlist = player.playlist = (nextPlaylist, newIndex = 0) => {
if (changing) {
throw new Error('do not call playlist() during a playlist change');
}
if (Array.isArray(nextPlaylist)) {
// @todo - Simplify this to `list.slice()` for v5.
const previousPlaylist = Array.isArray(list) ? list.slice() : null;
list = preparePlaylistItems(nextPlaylist); // Mark the playlist as changing during the duringplaylistchange lifecycle.
changing = true;
player.trigger({
type: 'duringplaylistchange',
nextIndex: newIndex,
nextPlaylist,
previousIndex: playlist.currentIndex_,
// @todo - Simplify this to simply pass along `previousPlaylist` for v5.
previousPlaylist: previousPlaylist || []
});
changing = false;
if (newIndex !== -1) {
playlist.currentItem(newIndex);
} // The only time the previous playlist is null is the first call to this
// function. This allows us to fire the `duringplaylistchange` event
// every time the playlist is populated and to maintain backward
// compatibility by not firing the `playlistchange` event on the initial
// population of the list.
//
// @todo - Remove this condition in preparation for v5.
if (previousPlaylist) {
player.setTimeout(() => {
player.trigger({
type: 'playlistchange',
action: 'change'
});
}, 0);
}
} // Always return a shallow clone of the playlist list.
// We also want to return originalValue if any item in the list has it.
return list.map(item => item.originalValue || item);
}; // On a new source, if there is no current item, disable auto-advance.
player.on('loadstart', () => {
if (playlist.currentItem() === -1) {
reset(player);
}
});
playlist.currentIndex_ = -1;
playlist.player_ = player;
playlist.autoadvance_ = {};
playlist.repeat_ = false;
playlist.currentPlaylistItemId_ = null;
/**
* Get or set the current item in the playlist.
*
* During the duringplaylistchange event, acts only as a getter.
*
* @param {number} [index]
* If given as a valid value, plays the playlist item at that index.
*
* @return {number}
* The current item index.
*/
playlist.currentItem = index => {
// If the playlist is changing, only act as a getter.
if (changing) {
return playlist.currentIndex_;
} // Act as a setter when the index is given and is a valid number.
if (typeof index === 'number' && playlist.currentIndex_ !== index && index >= 0 && index < list.length) {
playlist.currentIndex_ = index;
playItem(playlist.player_, list[playlist.currentIndex_]); // When playing multiple videos in a playlist the videojs PosterImage
// will be hidden using CSS. However, in some browsers the native poster
// attribute will briefly appear while the new source loads. Prevent
// this by hiding every poster after the first play list item. This
// doesn't cover every use case for showing/hiding the poster, but
// it will significantly improve the user experience.
if (index > 0) {
player.poster('');
}
return playlist.currentIndex_;
}
const src = playlist.player_.currentSrc() || ''; // If there is a currentPlaylistItemId_, validate that it matches the
// current source URL returned by the player. This is sufficient evidence
// to suggest that the source was set by the playlist plugin. This code
// exists primarily to deal with playlists where multiple items have the
// same source.
if (playlist.currentPlaylistItemId_) {
const indexInItemIds = indexInPlaylistItemIds(list, playlist.currentPlaylistItemId_);
const item = list[indexInItemIds]; // Found a match, this is our current index!
if (item && Array.isArray(item.sources) && indexInSources([item], src) > -1) {
playlist.currentIndex_ = indexInItemIds;
return playlist.currentIndex_;
} // If this does not match the current source, null it out so subsequent
// calls can skip this step.
playlist.currentPlaylistItemId_ = null;
} // Finally, if we don't have a valid, current playlist item ID, we can
// auto-detect it based on the player's current source URL.
playlist.currentIndex_ = playlist.indexOf(src);
return playlist.currentIndex_;
};
/**
* A custom DOM event that is fired when new item(s) are added to the current
* playlist (rather than replacing the entire playlist).
*
* Unlike playlistchange, this is fired synchronously as it does not
* affect playback.
*
* @typedef {Object} PlaylistAddEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistadd"
*
* @property {number} count
* The number of items that were added.
*
* @property {number} index
* The starting index where item(s) were added.
*/
/**
* A custom DOM event that is fired when new item(s) are removed from the
* current playlist (rather than replacing the entire playlist).
*
* This is fired synchronously as it does not affect playback.
*
* @typedef {Object} PlaylistRemoveEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistremove"
*
* @property {number} count
* The number of items that were removed.
*
* @property {number} index
* The starting index where item(s) were removed.
*/
/**
* Add one or more items to the playlist.
*
* @fires {PlaylistAddEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {string|Object|Array} item
* An item - or array of items - to be added to the playlist.
*
* @param {number} [index]
* If given as a valid value, injects the new playlist item(s)
* starting from that index. Otherwise, the item(s) are appended.
*/
playlist.add = (items, index) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
index = list.length;
}
if (!Array.isArray(items)) {
items = [items];
}
list.splice(index, 0, ...preparePlaylistItems(items)); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'add'
});
player.trigger({
type: 'playlistadd',
count: items.length,
index
});
};
/**
* Remove one or more items from the playlist.
*
* @fires {PlaylistRemoveEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {number} index
* If a valid index in the current playlist, removes the item at that
* index from the playlist.
*
* If no valid index is given, nothing is removed from the playlist.
*
* @param {number} [count=1]
* The number of items to remove from the playlist.
*/
playlist.remove = (index, count = 1) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
return;
}
list.splice(index, count); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'remove'
});
player.trigger({
type: 'playlistremove',
count,
index
});
};
/**
* Checks if the playlist contains a value.
*
* @param {string|Object|Array} value
* The value to check
*
* @return {boolean}
* The result
*/
playlist.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
*/
playlist.indexOf = value => {
if (typeof value === 'string') {
return indexInSources(list, value);
}
const sources = Array.isArray(value) ? value : value.sources;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (typeof source === 'string') {
return indexInSources(list, source);
} else if (source.src) {
return indexInSources(list, source.src);
}
}
return -1;
};
/**
* Get the index of the current item in the playlist. This is identical to
* calling `currentItem()` with no arguments.
*
* @return {number}
* The current item index.
*/
playlist.currentIndex = () => playlist.currentItem();
/**
* Get the index of the last item in the playlist.
*
* @return {number}
* The index of the last item in the playlist or -1 if there are no
* items.
*/
playlist.lastIndex = () => list.length - 1;
/**
* Get the index of the next item in the playlist.
*
* @return {number}
* The index of the next item in the playlist or -1 if there is no
* current item.
*/
playlist.nextIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
}
const lastIndex = playlist.lastIndex(); // When repeating, loop back to the beginning on the last item.
if (playlist.repeat_ && current === lastIndex) {
return 0;
} // Don't go past the end of the playlist.
return Math.min(current + 1, lastIndex);
};
/**
* Get the index of the previous item in the playlist.
*
* @return {number}
* The index of the previous item in the playlist or -1 if there is
* no current item.
*/
playlist.previousIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
} // When repeating, loop back to the end of the playlist.
if (playlist.repeat_ && current === 0) {
return playlist.lastIndex();
} // Don't go past the beginning of the playlist.
return Math.max(current - 1, 0);
};
/**
* Plays the first item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if the list is empty.
*/
playlist.first = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(0);
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
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.
*/
playlist.last = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(playlist.lastIndex());
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
playlist.currentIndex_ = -1;
};
/**
* Plays the next item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on last item.
*/
playlist.next = () => {
if (changing) {
return;
}
const index = playlist.nextIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Plays the previous item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on first item.
*/
playlist.previous = () => {
if (changing) {
return;
}
const index = playlist.previousIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Set up auto-advance on the playlist.
*
* @param {number} [delay]
* The number of seconds to wait before each auto-advance.
*/
playlist.autoadvance = 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
*/
playlist.repeat = val => {
if (val === undefined) {
return playlist.repeat_;
}
if (typeof val !== 'boolean') {
videojs.log.error('videojs-playlist: Invalid value for repeat', val);
return;
}
playlist.repeat_ = !!val;
return playlist.repeat_;
};
/**
* Sorts the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
* @fires playlistsorted
*
* @param {Function} compare
* A comparator function as per the native Array method.
*/
playlist.sort = compare => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.sort(compare); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Reverses the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse}
* @fires playlistsorted
*/
playlist.reverse = () => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.reverse(); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Shuffle the contents of the list randomly.
*
* @see {@link https://github.com/lodash/lodash/blob/40e096b6d5291a025e365a0f4c010d9a0efb9a69/shuffle.js}
* @fires playlistsorted
* @todo Make the `rest` option default to `true` in v5.0.0.
* @param {Object} [options]
* An object containing shuffle options.
*
* @param {boolean} [options.rest = false]
* By default, the entire playlist is randomized. However, this may
* not be desirable in all cases, such as when a user is already
* watching a video.
*
* When `true` is passed for this option, it will only shuffle
* playlist items after the current item. For example, when on the
* first item, will shuffle the second item and beyond.
*/
playlist.shuffle = ({
rest
} = {}) => {
let index = 0;
let arr = list; // When options.rest is true, start randomization at the item after the
// current item.
if (rest) {
index = playlist.currentIndex_ + 1;
arr = list.slice(index);
} // Bail if the array is empty or too short to shuffle.
if (arr.length <= 1) {
return;
}
randomize(arr); // When options.rest is true, splice the randomized sub-array back into
// the original array.
if (rest) {
list.splice(...[index, arr.length].concat(arr));
} // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
}; // If an initial list was given, populate the playlist with it.
if (Array.isArray(initialList)) {
playlist(initialList, initialIndex); // If there is no initial list given, silently set an empty array.
} else {
list = [];
}
return playlist;
}
var version = "5.1.0";
const 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
*/
const plugin = function (list, item) {
factory(this, list, item);
};
registerPlugin('playlist', plugin);
plugin.VERSION = version;
export { plugin as default };

992
node_modules/videojs-playlist/dist/videojs-playlist.js generated vendored Normal file
View file

@ -0,0 +1,992 @@
/*! @name videojs-playlist @version 5.1.0 @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 = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsPlaylist = factory(global.videojs));
})(this, (function (videojs) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
/**
* 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
*/
const validSeconds = s => 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
*/
let reset = player => {
const aa = player.playlist.autoadvance_;
if (aa.timeout) {
player.clearTimeout(aa.timeout);
}
if (aa.trigger) {
player.off('ended', aa.trigger);
}
aa.timeout = null;
aa.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
*/
const setup = (player, delay) => {
reset(player); // Before queuing up new auto-advance behavior, check if `seconds` was
// called with a valid value.
if (!validSeconds(delay)) {
player.playlist.autoadvance_.delay = null;
return;
}
player.playlist.autoadvance_.delay = delay;
player.playlist.autoadvance_.trigger = function () {
// This calls setup again, which will reset the existing auto-advance and
// set up another auto-advance for the next "ended" event.
const cancelOnPlay = () => setup(player, delay); // If there is a "play" event while we're waiting for an auto-advance,
// we need to cancel the auto-advance. This could mean the user seeked
// back into the content or restarted the content. This is reproducible
// with an auto-advance > 0.
player.one('play', cancelOnPlay);
player.playlist.autoadvance_.timeout = player.setTimeout(() => {
reset(player);
player.off('play', cancelOnPlay);
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
*/
const clearTracks = player => {
const tracks = player.remoteTextTracks();
let 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 {Object} item
* A source from the playlist.
*
* @return {Player}
* The player that is now playing the item
*/
const playItem = (player, item) => {
const replay = !player.paused() || player.ended();
player.trigger('beforeplaylistitem', item.originalValue || item);
if (item.playlistItemId_) {
player.playlist.currentPlaylistItemId_ = item.playlistItemId_;
}
player.poster(item.poster || '');
player.src(item.sources);
clearTracks(player);
player.ready(() => {
(item.textTracks || []).forEach(player.addRemoteTextTrack.bind(player));
player.trigger('playlistitem', item.originalValue || item);
if (replay) {
const playPromise = player.play(); // silence error when a pause interrupts a play request
// on browsers which return a promise
if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
playPromise.then(null, e => {});
}
}
setup(player, player.playlist.autoadvance_.delay);
});
return player;
};
let guid = 1;
/**
* Transform any primitive playlist item value into an object.
*
* For non-object values, adds a property to the transformed item containing
* original value passed.
*
* For all items, add a unique ID to each playlist item object. This id is
* used to determine the index of an item in the playlist array in cases where
* there are multiple otherwise identical items.
*
* @param {Object} newItem
* An playlist item object, but accepts any value.
*
* @return {Object}
*/
const preparePlaylistItem = newItem => {
let item = newItem;
if (!newItem || typeof newItem !== 'object') {
// Casting to an Object in this way allows primitives to retain their
// primitiveness (i.e. they will be cast back to primitives as needed).
item = Object(newItem);
item.originalValue = newItem;
}
item.playlistItemId_ = guid++;
return item;
};
/**
* Look through an array of playlist items and passes them to
* preparePlaylistItem.
*
* @private
*
* @param {Array} arr
* An array of playlist items
*
* @return {Array}
* A new array with transformed items
*/
const preparePlaylistItems = arr => arr.map(preparePlaylistItem);
/**
* Look through an array of playlist items for a specific playlist item id.
*
* @private
* @param {Array} list
* An array of playlist items to look through
*
* @param {number} currentItemId
* The current item ID.
*
* @return {number}
* The index of the playlist item or -1 if not found
*/
const indexInPlaylistItemIds = (list, currentItemId) => {
for (let i = 0; i < list.length; i++) {
if (list[i].playlistItemId_ === currentItemId) {
return i;
}
}
return -1;
};
/**
* 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
*/
const sourceEquals = (source1, source2) => {
let src1 = source1;
let src2 = source2;
if (typeof source1 === 'object') {
src1 = source1.src;
}
if (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
*/
const indexInSources = (arr, src) => {
for (let i = 0; i < arr.length; i++) {
const sources = arr[i].sources;
if (Array.isArray(sources)) {
for (let j = 0; j < sources.length; j++) {
const source = sources[j];
if (source && sourceEquals(source, src)) {
return i;
}
}
}
}
return -1;
};
/**
* Randomize the contents of an array.
*
* @private
* @param {Array} arr
* An array.
*
* @return {Array}
* The same array that was passed in.
*/
const randomize = arr => {
let index = -1;
const lastIndex = arr.length - 1;
while (++index < arr.length) {
const rand = index + Math.floor(Math.random() * (lastIndex - index + 1));
const value = arr[rand];
arr[rand] = arr[index];
arr[index] = value;
}
return arr;
};
/**
* 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.
*/
function factory(player, initialList, initialIndex = 0) {
let list = null;
let changing = false;
/**
* 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
*/
const playlist = player.playlist = (nextPlaylist, newIndex = 0) => {
if (changing) {
throw new Error('do not call playlist() during a playlist change');
}
if (Array.isArray(nextPlaylist)) {
// @todo - Simplify this to `list.slice()` for v5.
const previousPlaylist = Array.isArray(list) ? list.slice() : null;
list = preparePlaylistItems(nextPlaylist); // Mark the playlist as changing during the duringplaylistchange lifecycle.
changing = true;
player.trigger({
type: 'duringplaylistchange',
nextIndex: newIndex,
nextPlaylist,
previousIndex: playlist.currentIndex_,
// @todo - Simplify this to simply pass along `previousPlaylist` for v5.
previousPlaylist: previousPlaylist || []
});
changing = false;
if (newIndex !== -1) {
playlist.currentItem(newIndex);
} // The only time the previous playlist is null is the first call to this
// function. This allows us to fire the `duringplaylistchange` event
// every time the playlist is populated and to maintain backward
// compatibility by not firing the `playlistchange` event on the initial
// population of the list.
//
// @todo - Remove this condition in preparation for v5.
if (previousPlaylist) {
player.setTimeout(() => {
player.trigger({
type: 'playlistchange',
action: 'change'
});
}, 0);
}
} // Always return a shallow clone of the playlist list.
// We also want to return originalValue if any item in the list has it.
return list.map(item => item.originalValue || item);
}; // On a new source, if there is no current item, disable auto-advance.
player.on('loadstart', () => {
if (playlist.currentItem() === -1) {
reset(player);
}
});
playlist.currentIndex_ = -1;
playlist.player_ = player;
playlist.autoadvance_ = {};
playlist.repeat_ = false;
playlist.currentPlaylistItemId_ = null;
/**
* Get or set the current item in the playlist.
*
* During the duringplaylistchange event, acts only as a getter.
*
* @param {number} [index]
* If given as a valid value, plays the playlist item at that index.
*
* @return {number}
* The current item index.
*/
playlist.currentItem = index => {
// If the playlist is changing, only act as a getter.
if (changing) {
return playlist.currentIndex_;
} // Act as a setter when the index is given and is a valid number.
if (typeof index === 'number' && playlist.currentIndex_ !== index && index >= 0 && index < list.length) {
playlist.currentIndex_ = index;
playItem(playlist.player_, list[playlist.currentIndex_]); // When playing multiple videos in a playlist the videojs PosterImage
// will be hidden using CSS. However, in some browsers the native poster
// attribute will briefly appear while the new source loads. Prevent
// this by hiding every poster after the first play list item. This
// doesn't cover every use case for showing/hiding the poster, but
// it will significantly improve the user experience.
if (index > 0) {
player.poster('');
}
return playlist.currentIndex_;
}
const src = playlist.player_.currentSrc() || ''; // If there is a currentPlaylistItemId_, validate that it matches the
// current source URL returned by the player. This is sufficient evidence
// to suggest that the source was set by the playlist plugin. This code
// exists primarily to deal with playlists where multiple items have the
// same source.
if (playlist.currentPlaylistItemId_) {
const indexInItemIds = indexInPlaylistItemIds(list, playlist.currentPlaylistItemId_);
const item = list[indexInItemIds]; // Found a match, this is our current index!
if (item && Array.isArray(item.sources) && indexInSources([item], src) > -1) {
playlist.currentIndex_ = indexInItemIds;
return playlist.currentIndex_;
} // If this does not match the current source, null it out so subsequent
// calls can skip this step.
playlist.currentPlaylistItemId_ = null;
} // Finally, if we don't have a valid, current playlist item ID, we can
// auto-detect it based on the player's current source URL.
playlist.currentIndex_ = playlist.indexOf(src);
return playlist.currentIndex_;
};
/**
* A custom DOM event that is fired when new item(s) are added to the current
* playlist (rather than replacing the entire playlist).
*
* Unlike playlistchange, this is fired synchronously as it does not
* affect playback.
*
* @typedef {Object} PlaylistAddEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistadd"
*
* @property {number} count
* The number of items that were added.
*
* @property {number} index
* The starting index where item(s) were added.
*/
/**
* A custom DOM event that is fired when new item(s) are removed from the
* current playlist (rather than replacing the entire playlist).
*
* This is fired synchronously as it does not affect playback.
*
* @typedef {Object} PlaylistRemoveEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistremove"
*
* @property {number} count
* The number of items that were removed.
*
* @property {number} index
* The starting index where item(s) were removed.
*/
/**
* Add one or more items to the playlist.
*
* @fires {PlaylistAddEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {string|Object|Array} item
* An item - or array of items - to be added to the playlist.
*
* @param {number} [index]
* If given as a valid value, injects the new playlist item(s)
* starting from that index. Otherwise, the item(s) are appended.
*/
playlist.add = (items, index) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
index = list.length;
}
if (!Array.isArray(items)) {
items = [items];
}
list.splice(index, 0, ...preparePlaylistItems(items)); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'add'
});
player.trigger({
type: 'playlistadd',
count: items.length,
index
});
};
/**
* Remove one or more items from the playlist.
*
* @fires {PlaylistRemoveEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {number} index
* If a valid index in the current playlist, removes the item at that
* index from the playlist.
*
* If no valid index is given, nothing is removed from the playlist.
*
* @param {number} [count=1]
* The number of items to remove from the playlist.
*/
playlist.remove = (index, count = 1) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
return;
}
list.splice(index, count); // playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({
type: 'playlistchange',
action: 'remove'
});
player.trigger({
type: 'playlistremove',
count,
index
});
};
/**
* Checks if the playlist contains a value.
*
* @param {string|Object|Array} value
* The value to check
*
* @return {boolean}
* The result
*/
playlist.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
*/
playlist.indexOf = value => {
if (typeof value === 'string') {
return indexInSources(list, value);
}
const sources = Array.isArray(value) ? value : value.sources;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (typeof source === 'string') {
return indexInSources(list, source);
} else if (source.src) {
return indexInSources(list, source.src);
}
}
return -1;
};
/**
* Get the index of the current item in the playlist. This is identical to
* calling `currentItem()` with no arguments.
*
* @return {number}
* The current item index.
*/
playlist.currentIndex = () => playlist.currentItem();
/**
* Get the index of the last item in the playlist.
*
* @return {number}
* The index of the last item in the playlist or -1 if there are no
* items.
*/
playlist.lastIndex = () => list.length - 1;
/**
* Get the index of the next item in the playlist.
*
* @return {number}
* The index of the next item in the playlist or -1 if there is no
* current item.
*/
playlist.nextIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
}
const lastIndex = playlist.lastIndex(); // When repeating, loop back to the beginning on the last item.
if (playlist.repeat_ && current === lastIndex) {
return 0;
} // Don't go past the end of the playlist.
return Math.min(current + 1, lastIndex);
};
/**
* Get the index of the previous item in the playlist.
*
* @return {number}
* The index of the previous item in the playlist or -1 if there is
* no current item.
*/
playlist.previousIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
} // When repeating, loop back to the end of the playlist.
if (playlist.repeat_ && current === 0) {
return playlist.lastIndex();
} // Don't go past the beginning of the playlist.
return Math.max(current - 1, 0);
};
/**
* Plays the first item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if the list is empty.
*/
playlist.first = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(0);
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
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.
*/
playlist.last = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(playlist.lastIndex());
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
playlist.currentIndex_ = -1;
};
/**
* Plays the next item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on last item.
*/
playlist.next = () => {
if (changing) {
return;
}
const index = playlist.nextIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Plays the previous item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on first item.
*/
playlist.previous = () => {
if (changing) {
return;
}
const index = playlist.previousIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Set up auto-advance on the playlist.
*
* @param {number} [delay]
* The number of seconds to wait before each auto-advance.
*/
playlist.autoadvance = 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
*/
playlist.repeat = val => {
if (val === undefined) {
return playlist.repeat_;
}
if (typeof val !== 'boolean') {
videojs__default["default"].log.error('videojs-playlist: Invalid value for repeat', val);
return;
}
playlist.repeat_ = !!val;
return playlist.repeat_;
};
/**
* Sorts the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
* @fires playlistsorted
*
* @param {Function} compare
* A comparator function as per the native Array method.
*/
playlist.sort = compare => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.sort(compare); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Reverses the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse}
* @fires playlistsorted
*/
playlist.reverse = () => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.reverse(); // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Shuffle the contents of the list randomly.
*
* @see {@link https://github.com/lodash/lodash/blob/40e096b6d5291a025e365a0f4c010d9a0efb9a69/shuffle.js}
* @fires playlistsorted
* @todo Make the `rest` option default to `true` in v5.0.0.
* @param {Object} [options]
* An object containing shuffle options.
*
* @param {boolean} [options.rest = false]
* By default, the entire playlist is randomized. However, this may
* not be desirable in all cases, such as when a user is already
* watching a video.
*
* When `true` is passed for this option, it will only shuffle
* playlist items after the current item. For example, when on the
* first item, will shuffle the second item and beyond.
*/
playlist.shuffle = ({
rest
} = {}) => {
let index = 0;
let arr = list; // When options.rest is true, start randomization at the item after the
// current item.
if (rest) {
index = playlist.currentIndex_ + 1;
arr = list.slice(index);
} // Bail if the array is empty or too short to shuffle.
if (arr.length <= 1) {
return;
}
randomize(arr); // When options.rest is true, splice the randomized sub-array back into
// the original array.
if (rest) {
list.splice(...[index, arr.length].concat(arr));
} // If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
}; // If an initial list was given, populate the playlist with it.
if (Array.isArray(initialList)) {
playlist(initialList, initialIndex); // If there is no initial list given, silently set an empty array.
} else {
list = [];
}
return playlist;
}
var version = "5.1.0";
const registerPlugin = videojs__default["default"].registerPlugin || videojs__default["default"].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
*/
const plugin = function (list, item) {
factory(this, list, item);
};
registerPlugin('playlist', plugin);
plugin.VERSION = version;
return plugin;
}));

File diff suppressed because one or more lines are too long

599
node_modules/videojs-playlist/docs/api.md generated vendored Normal file
View file

@ -0,0 +1,599 @@
# video.js Playlist API
## Playlist Item Object
A playlist is an array of playlist items. Usually, a playlist item is an object with an array of `sources`, but it could also be a primitive value like a string.
## Methods
### `player.playlist([Array newList], [Number newIndex]) -> Array`
Get or set the current playlist for a player.
If called without arguments, it is a getter. With an argument, it is a setter.
```js
player.playlist([{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}, {
sources: [{
src: '//vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
}],
poster: '//www.videojs.com/img/poster.jpg'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/movie.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/video/movie_300.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/video/poster.png'
}]);
// [{ ... }, ... ]
player.playlist([{
sources: [{
src: '//media.w3.org/2010/05/video/movie_300.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/video/poster.png'
}]);
// [{ ... }]
```
If a setter, a second argument sets the video to be loaded. If omitted, the
first video is loaded. If `-1`, no video is loaded.
```js
player.playlist([{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}], 1);
// [{ ... }]
```
#### `player.playlist.currentItem([Number index]) -> Number`
Get or set the current item index.
If called without arguments, it is a getter. With an argument, it is a setter.
If the player is currently playing a non-playlist video, it will return `-1`.
```js
var samplePlaylist = [{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}];
player.playlist(samplePlaylist);
player.currentItem();
// 0
player.currentItem(2);
// 2
player.src('//example.com/video.mp4');
player.playlist.currentItem();
// -1
```
#### `player.playlist.add(String|Object|Array items, [Number index])`
Adds one or more items to the current playlist without replacing the playlist.
Fires the `playlistadd` event.
Calling this method during the `duringplaylistchange` event throws an error.
```js
var samplePlaylist = [{
sources: [{
src: 'sintel.mp4',
type: 'video/mp4'
}]
}];
player.playlist(samplePlaylist);
// Playlist will contain two items after this call: sintel.mp4, bbb.mp4
player.add({
sources: [{
src: 'bbb.mp4',
type: 'video/mp4'
}]
});
// Playlist will contain four items after this call: sintel.mp4, tears.mp4, test.mp4, and bbb.mp4
player.add([{
sources: [{
src: 'tears.mp4',
type: 'video/mp4'
}]
}, {
sources: [{
src: 'test.mp4',
type: 'video/mp4'
}]
}], 1);
```
#### `player.playlist.remove(Number index, [Number count=1])`
Removes one or more items from the current playlist without replacing the playlist. By default, if `count` is not provided, one item will be removed.
Fires the `playlistremove` event.
Calling this method during the `duringplaylistchange` event throws an error.
```js
var samplePlaylist = [{
sources: [{
src: 'sintel.mp4',
type: 'video/mp4'
}]
}, {
sources: [{
src: 'bbb.mp4',
type: 'video/mp4'
}]
}, {
sources: [{
src: 'tears.mp4',
type: 'video/mp4'
}]
}, {
sources: [{
src: 'test.mp4',
type: 'video/mp4'
}]
}];
player.playlist(samplePlaylist);
// Playlist will contain three items after this call: bbb.mp4, tears.mp4, and test.mp4
player.remove(0);
// Playlist will contain one item after this call: bbb.mp4
player.remove(1, 2);
```
#### `player.playlist.contains(String|Object|Array value) -> Boolean`
Determine whether a string, source object, or playlist item is contained within a playlist.
Assuming the playlist used above, consider the following example:
```js
player.playlist.contains('//media.w3.org/2010/05/sintel/trailer.mp4');
// true
player.playlist.contains([{
src: '//media.w3.org/2010/05/sintel/poster.png',
type: 'image/png'
}]);
// false
player.playlist.contains({
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}]
});
// true
```
#### `player.playlist.indexOf(String|Object|Array value) -> Number`
Get the index of a string, source object, or playlist item in the playlist. If not found, returns `-1`.
Assuming the playlist used above, consider the following example:
```js
player.playlist.indexOf('//media.w3.org/2010/05/bunny/trailer.mp4');
// 1
player.playlist.indexOf([{
src: '//media.w3.org/2010/05/bunny/movie.mp4',
type: 'video/mp4'
}]);
// 3
player.playlist.indexOf({
sources: [{
src: '//media.w3.org/2010/05/video/movie_300.mp4',
type: 'video/mp4'
}]
});
// 4
```
#### `player.playlist.currentIndex() -> Number`
Get the index of the current item in the playlist. This is identical to calling `currentItem()` with no arguments.
```js
var samplePlaylist = [{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}];
player.currentIndex();
// 0
```
#### `player.playlist.nextIndex() -> Number`
Get the index of the next item in the playlist.
If the player is on the last item, returns the last item's index. However, if the playlist repeats and is on the last item, returns `0`.
If the player is currently playing a non-playlist video, it will return `-1`.
```js
var samplePlaylist = [{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}];
player.playlist(samplePlaylist);
player.nextIndex();
// 1
player.next();
player.nextIndex();
// 1
player.repeat(true);
player.nextIndex();
// 0
player.src('//example.com/video.mp4');
player.playlist.nextIndex();
// -1
```
#### `player.playlist.previousIndex() -> Number`
Get the index of the previous item in the playlist.
If the player is on the first item, returns `0`. However, if the playlist repeats and is on the first item, returns the last item's index.
If the player is currently playing a non-playlist video, it will return `-1`.
```js
var samplePlaylist = [{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}];
player.playlist(samplePlaylist, 1);
player.previousIndex();
// 0
player.previous();
player.previousIndex();
// 0
player.repeat(true);
player.previousIndex();
// 1
player.src('//example.com/video.mp4');
player.playlist.previousIndex();
// -1
```
#### `player.playlist.lastIndex() -> Number`
Get the index of the last item in the playlist.
```js
var samplePlaylist = [{
sources: [{
src: '//media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: '//media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: '//media.w3.org/2010/05/bunny/poster.png'
}];
player.lastIndex();
// 1
```
#### `player.playlist.first() -> Object|undefined`
Play the first item in the playlist.
Returns the activated playlist item unless the playlist is empty (in which case, returns `undefined`).
```js
player.playlist.first();
// { ... }
player.playlist([]);
player.playlist.first();
// undefined
```
#### `player.playlist.last() -> Object|undefined`
Play the last item in the playlist.
Returns the activated playlist item unless the playlist is empty (in which case, returns `undefined`).
```js
player.playlist.last();
// { ... }
player.playlist([]);
player.playlist.last();
// undefined
```
#### `player.playlist.next() -> Object`
Advance to the next item in the playlist.
Returns the activated playlist item unless the playlist is at the end (in which case, returns `undefined`).
```js
player.playlist.next();
// { ... }
player.playlist.last();
player.playlist.next();
// undefined
```
#### `player.playlist.previous() -> Object`
Go back to the previous item in the playlist.
Returns the activated playlist item unless the playlist is at the beginning (in which case, returns `undefined`).
```js
player.playlist.next();
// { ... }
player.playlist.previous();
// { ... }
player.playlist.first();
// { ... }
player.playlist.previous();
// undefined
```
#### `player.playlist.autoadvance([Number delay]) -> undefined`
Sets up playlist auto-advance behavior.
Once invoked, at the end of each video in the playlist, the plugin will wait `delay` seconds before proceeding automatically to the next video.
Any value which is not a positive, finite integer, will be treated as a request to cancel and reset the auto-advance behavior.
If you change auto-advance during a delay, the auto-advance will be canceled and it will not advance the next video, but it will use the new timeout value for the following videos.
```js
// no wait before loading in the next item
player.playlist.autoadvance(0);
// wait 5 seconds before loading in the next item
player.playlist.autoadvance(5);
// reset and cancel the auto-advance
player.playlist.autoadvance();
```
#### `player.playlist.repeat([Boolean val]) -> Boolean`
Enable or disable repeat by passing true or false as the argument.
When repeat is enabled, the "next" video after the final video in the playlist is the first video in the playlist. This affects the behavior of calling `next()`, of autoadvance, and so on.
This method returns the current value. Call with no argument to use as a getter.
Examples:
```js
player.playlist.repeat(true);
player.playlist.repeat();
// true
player.playlist.repeat(false);
player.playlist.repeat();
// false
```
#### `player.playlist.sort([Function compare]) -> undefined`
Sort the playlist in a manner identical to [`Array#sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
Fires the `playlistsorted` event after sorting.
#### `player.playlist.reverse() -> undefined`
Reverse the playlist in a manner identical to [`Array#reverse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse).
Fires the `playlistsorted` event after reversing.
#### `player.playlist.shuffle() -> undefined`
Shuffles/randomizes the order of playlist items in a manner identical to [`lodash.shuffle`](https://lodash.com/docs/4.17.4#shuffle).
Fires the `playlistsorted` event after shuffling.
## Events
### `duringplaylistchange`
This event is fired _after_ the contents of the playlist are changed when calling `playlist()`, but _before_ the current playlist item is changed. The event object has several special properties:
- `nextIndex`: The index from the next playlist that will be played first.
- `nextPlaylist`: A shallow clone of the next playlist.
- `previousIndex`: The index from the previous playlist (will always match the current index when this event triggers, but is provided for completeness).
- `previousPlaylist`: A shallow clone of the previous playlist.
**NOTE**: This event fires every time `player.playlist()` is called - including the first time.
#### Caveats
During the firing of this event, the playlist is considered to be in a **changing state**, which has the following effects:
- Calling the main playlist method (i.e. `player.playlist([...])`) will throw an error.
- Playlist navigation methods - `first`, `last`, `next`, and `previous` - are rendered inoperable.
- The `currentItem()` method only acts as a getter.
- While the sorting methods - `sort`, `reverse`, and `shuffle` - will continue to work, they do not fire the `playlistsorted` event.
#### Why have this event?
This event provides an opportunity to intercept the playlist setting process before a new source is set on the player and before the `playlistchange` event fires, while providing a consistent playlist API.
One use-case might be shuffling a playlist that has just come from a server, but before its initial source is loaded into the player or the playlist UI is updated:
```js
player.on('duringplaylistchange', function() {
// Remember, this will not trigger a "playlistsorted" event!
player.playlist.shuffle();
});
player.on('playlistchange', function() {
videojs.log('The playlist was shuffled, so the UI can be updated.');
});
```
### `playlistchange`
This event is fired whenever the contents of the playlist are changed (i.e., when `player.playlist()` is called with an argument) - except the first time. Additionally, it is fired when item(s) are added or removed from the playlist.
In cases where a change to the playlist may result in a new video source being loaded, it is fired asynchronously to let the browser start loading the first video in the new playlist.
```js
player.on('playlistchange', function() {
player.playlist();
});
player.playlist([]);
// [ ... ]
player.playlist([]);
// [ ... ]
```
#### `action`
Each `playlistchange` event object has an additional `action` property. This can help you identify the action that caused the `playlistchange` event to be triggered. It will be one of:
* `add`: One or more items were added to the playlist
* `change`: The entire playlist was replaced
* `remove`: One or more items were removed from the playlist
This is considered temporary/deprecated behavior because future implementations should only fire the `playlistadd` and `playlistremove` events.
#### Backward Compatibility
This event _does not fire_ the first time `player.playlist()` is called. If you want it to fire on the first call to `player.playlist()`, you can call it without an argument before calling it with one:
```js
// This will fire no events.
player.playlist();
// This will fire both "duringplaylistchange" and "playlistchange"
player.playlist([...]);
```
This behavior will be removed in v5.0.0 and the event will fire in all cases.
### `playlistadd`
One or more items were added to the playlist via the `playlist.add()` method.
### `playlistremove`
One or more items were removed from the playlist via the `playlist.remove()` method.
### `beforeplaylistitem`
This event is fired before switching to a new content source within a playlist (i.e., when any of `currentItem()`, `first()`, or `last()` is called, but before the player's state has been changed).
### `playlistitem`
This event is fired when switching to a new content source within a playlist (i.e., when any of `currentItem()`, `first()`, or `last()` is called; after the player's state has been changed, but before playback has been resumed).
### `playlistsorted`
This event is fired when any method is called that changes the order of playlist items - `sort()`, `reverse()`, or `shuffle()`.

1
node_modules/videojs-playlist/docs/index.md generated vendored Normal file
View file

@ -0,0 +1 @@
# Documentation for videojs-playlist

114
node_modules/videojs-playlist/index.html generated vendored Normal file
View file

@ -0,0 +1,114 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-playlist Demo</title>
<link href="/node_modules/video.js/dist/video-js.css" rel="stylesheet">
<style>
button {
margin: 1em;
width: 10em;
}
.options {
margin: 1em;
}
.autoadvance-group label {
margin: 10px;
}
</style>
</head>
<body>
<video class="video-js vjs-default-skin" controls width="640px" height="360px"></video>
<button class="previous">Previous</button>
<button class="next">Next</button>
<div class="options">
<div class="autoadvance-group">
<h4>Auto-advance (in seconds)</h4>
<label><input type="radio" name="autoadvance" value="null" checked> No auto-advance</label>
<label><input type="radio" name="autoadvance" value="0"> 0</label>
<label><input type="radio" name="autoadvance" value="5"> 5</label>
<label><input type="radio" name="autoadvance" value="10"> 10</label>
<label><input type="radio" name="autoadvance" value="30"> 30</label>
</div>
<div class="repeat-group">
<h4>Repeat</h4>
<label><input class="repeat" type="checkbox" name="repeat" > Enable Repeat</label>
</div>
</div>
<ul>
<li><a href="/test/debug.html">Run unit tests in browser.</a></li>
</ul>
<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/dist/videojs-playlist.js"></script>
<script>
var videoList = [{
sources: [{
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
}],
poster: 'http://www.videojs.com/img/poster.jpg'
}, {
sources: [{
src: 'http://media.w3.org/2010/05/video/movie_300.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/video/poster.png'
}];
var player = videojs(document.querySelector('video'), {
inactivityTimeout: 0
});
try {
// try on ios
player.volume(0);
} catch (e) {}
player.on([
'duringplaylistchange',
'playlistchange',
'beforeplaylistitem',
'playlistitem',
'playlistsorted'
], function(e) {
videojs.log('player saw "' + e.type + '"');
});
player.playlist(videoList);
document.querySelector('.previous').addEventListener('click', function() {
player.playlist.previous();
});
document.querySelector('.next').addEventListener('click', function() {
player.playlist.next();
});
Array.prototype.forEach.call(document.querySelectorAll('[name=autoadvance]'), function(el) {
el.addEventListener('click', function() {
var value = document.querySelector('[name=autoadvance]:checked').value;
player.playlist.autoadvance(Number(value));
});
});
document.querySelector('[name="autoadvance"][value="null"]').click();
var repeatCheckbox = document.querySelector('.repeat');
repeatCheckbox.addEventListener('click', function() {
player.playlist.repeat(this.checked);
});
repeatCheckbox.checked = false;
</script>
</body>
</html>

98
node_modules/videojs-playlist/package.json generated vendored Normal file
View file

@ -0,0 +1,98 @@
{
"name": "videojs-playlist",
"version": "5.1.0",
"description": "Playlist plugin for Video.js",
"main": "dist/videojs-playlist.cjs.js",
"scripts": {
"prebuild": "npm run clean",
"build": "npm-run-all -p build:*",
"build:js": "rollup -c scripts/rollup.config.js",
"clean": "shx rm -rf ./dist ./test/dist",
"postclean": "shx mkdir -p ./dist ./test/dist",
"docs": "doctoc README.md",
"lint": "vjsstandard",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"start": "npm-run-all -p server watch",
"pretest": "npm-run-all lint build",
"test": "karma start scripts/karma.conf.js",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"preversion": "npm test",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:js": "npm run build:js -- -w",
"posttest": "shx cat test/dist/coverage/text.txt",
"prepublishOnly": "npm run build && vjsverify --skip-es-check"
},
"keywords": [
"playlist",
"videojs",
"videojs-plugin"
],
"author": "Brightcove, Inc.",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/brightcove/videojs-playlist"
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"dependencies": {
"global": "^4.3.2",
"video.js": "^6 || ^7 || ^8"
},
"devDependencies": {
"conventional-changelog-cli": "^2.0.1",
"conventional-changelog-videojs": "^3.0.0",
"doctoc": "^1.3.1",
"husky": "^1.0.0-rc.13",
"karma": "^3.0.0",
"lint-staged": "^8.1.0",
"not-prerelease": "^1.0.1",
"npm-merge-driver-install": "^1.0.0",
"npm-run-all": "^4.1.5",
"pkg-ok": "^2.2.0",
"rollup": "^2.61.1",
"shx": "^0.3.2",
"sinon": "^7.3.0",
"videojs-generate-karma-config": "~5.0.0",
"videojs-generate-rollup-config": "^7.0.0",
"videojs-generator-verify": "^4.0.1",
"videojs-standard": "^9.0.1"
},
"generator-videojs-plugin": {
"version": "7.3.2"
},
"engines": {
"node": ">=4.4.0"
},
"module": "dist/videojs-playlist.es.js",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
},
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"npm run docs:toc",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

13
node_modules/videojs-playlist/scripts/karma.conf.js generated vendored Normal file
View file

@ -0,0 +1,13 @@
const generate = require('videojs-generate-karma-config');
module.exports = function(config) {
// see https://github.com/videojs/videojs-generate-karma-config
// for options
const options = {};
config = generate(config, options);
// any other custom stuff not supported by options here!
};

11
node_modules/videojs-playlist/scripts/rollup.config.js generated vendored Normal file
View file

@ -0,0 +1,11 @@
const generate = require('videojs-generate-rollup-config');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
const options = {};
const config = generate(options);
// Add additonal builds/customization here!
// export the builds to rollup
export default Object.values(config.builds);

97
node_modules/videojs-playlist/src/auto-advance.js generated vendored Normal file
View file

@ -0,0 +1,97 @@
/**
* 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
*/
const validSeconds = s =>
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
*/
let reset = (player) => {
const aa = player.playlist.autoadvance_;
if (aa.timeout) {
player.clearTimeout(aa.timeout);
}
if (aa.trigger) {
player.off('ended', aa.trigger);
}
aa.timeout = null;
aa.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
*/
const setup = (player, delay) => {
reset(player);
// Before queuing up new auto-advance behavior, check if `seconds` was
// called with a valid value.
if (!validSeconds(delay)) {
player.playlist.autoadvance_.delay = null;
return;
}
player.playlist.autoadvance_.delay = delay;
player.playlist.autoadvance_.trigger = function() {
// This calls setup again, which will reset the existing auto-advance and
// set up another auto-advance for the next "ended" event.
const cancelOnPlay = () => setup(player, delay);
// If there is a "play" event while we're waiting for an auto-advance,
// we need to cancel the auto-advance. This could mean the user seeked
// back into the content or restarted the content. This is reproducible
// with an auto-advance > 0.
player.one('play', cancelOnPlay);
player.playlist.autoadvance_.timeout = player.setTimeout(() => {
reset(player);
player.off('play', cancelOnPlay);
player.playlist.next();
}, delay * 1000);
};
player.one('ended', player.playlist.autoadvance_.trigger);
};
/**
* Used to change the reset function in this module at runtime
* This should only be used in tests.
*
* @param {Function} fn
* The function to se the reset to
*/
const setReset_ = (fn) => {
reset = fn;
};
export {
setReset_,
reset,
setup
};

67
node_modules/videojs-playlist/src/play-item.js generated vendored Normal file
View file

@ -0,0 +1,67 @@
import {setup} from './auto-advance.js';
/**
* Removes all remote text tracks from a player.
*
* @param {Player} player
* The player to clear tracks on
*/
const clearTracks = (player) => {
const tracks = player.remoteTextTracks();
let 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 {Object} item
* A source from the playlist.
*
* @return {Player}
* The player that is now playing the item
*/
const playItem = (player, item) => {
const replay = !player.paused() || player.ended();
player.trigger('beforeplaylistitem', item.originalValue || item);
if (item.playlistItemId_) {
player.playlist.currentPlaylistItemId_ = item.playlistItemId_;
}
player.poster(item.poster || '');
player.src(item.sources);
clearTracks(player);
player.ready(() => {
(item.textTracks || []).forEach(player.addRemoteTextTrack.bind(player));
player.trigger('playlistitem', item.originalValue || item);
if (replay) {
const playPromise = player.play();
// silence error when a pause interrupts a play request
// on browsers which return a promise
if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
playPromise.then(null, (e) => {});
}
}
setup(player, player.playlist.autoadvance_.delay);
});
return player;
};
export default playItem;
export {clearTracks};

805
node_modules/videojs-playlist/src/playlist-maker.js generated vendored Normal file
View file

@ -0,0 +1,805 @@
import videojs from 'video.js';
import playItem from './play-item';
import * as autoadvance from './auto-advance';
// Shared incrementing GUID for playlist items.
let guid = 1;
/**
* Transform any primitive playlist item value into an object.
*
* For non-object values, adds a property to the transformed item containing
* original value passed.
*
* For all items, add a unique ID to each playlist item object. This id is
* used to determine the index of an item in the playlist array in cases where
* there are multiple otherwise identical items.
*
* @param {Object} newItem
* An playlist item object, but accepts any value.
*
* @return {Object}
*/
const preparePlaylistItem = (newItem) => {
let item = newItem;
if (!newItem || typeof newItem !== 'object') {
// Casting to an Object in this way allows primitives to retain their
// primitiveness (i.e. they will be cast back to primitives as needed).
item = Object(newItem);
item.originalValue = newItem;
}
item.playlistItemId_ = guid++;
return item;
};
/**
* Look through an array of playlist items and passes them to
* preparePlaylistItem.
*
* @private
*
* @param {Array} arr
* An array of playlist items
*
* @return {Array}
* A new array with transformed items
*/
const preparePlaylistItems = (arr) => arr.map(preparePlaylistItem);
/**
* Look through an array of playlist items for a specific playlist item id.
*
* @private
* @param {Array} list
* An array of playlist items to look through
*
* @param {number} currentItemId
* The current item ID.
*
* @return {number}
* The index of the playlist item or -1 if not found
*/
const indexInPlaylistItemIds = (list, currentItemId) => {
for (let i = 0; i < list.length; i++) {
if (list[i].playlistItemId_ === currentItemId) {
return i;
}
}
return -1;
};
/**
* 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
*/
const sourceEquals = (source1, source2) => {
let src1 = source1;
let src2 = source2;
if (typeof source1 === 'object') {
src1 = source1.src;
}
if (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
*/
const indexInSources = (arr, src) => {
for (let i = 0; i < arr.length; i++) {
const sources = arr[i].sources;
if (Array.isArray(sources)) {
for (let j = 0; j < sources.length; j++) {
const source = sources[j];
if (source && sourceEquals(source, src)) {
return i;
}
}
}
}
return -1;
};
/**
* Randomize the contents of an array.
*
* @private
* @param {Array} arr
* An array.
*
* @return {Array}
* The same array that was passed in.
*/
const randomize = (arr) => {
let index = -1;
const lastIndex = arr.length - 1;
while (++index < arr.length) {
const rand = index + Math.floor(Math.random() * (lastIndex - index + 1));
const value = arr[rand];
arr[rand] = arr[index];
arr[index] = value;
}
return arr;
};
/**
* 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.
*/
export default function factory(player, initialList, initialIndex = 0) {
let list = null;
let changing = false;
/**
* 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
*/
const playlist = player.playlist = (nextPlaylist, newIndex = 0) => {
if (changing) {
throw new Error('do not call playlist() during a playlist change');
}
if (Array.isArray(nextPlaylist)) {
// @todo - Simplify this to `list.slice()` for v5.
const previousPlaylist = Array.isArray(list) ? list.slice() : null;
list = preparePlaylistItems(nextPlaylist);
// Mark the playlist as changing during the duringplaylistchange lifecycle.
changing = true;
player.trigger({
type: 'duringplaylistchange',
nextIndex: newIndex,
nextPlaylist,
previousIndex: playlist.currentIndex_,
// @todo - Simplify this to simply pass along `previousPlaylist` for v5.
previousPlaylist: previousPlaylist || []
});
changing = false;
if (newIndex !== -1) {
playlist.currentItem(newIndex);
}
// The only time the previous playlist is null is the first call to this
// function. This allows us to fire the `duringplaylistchange` event
// every time the playlist is populated and to maintain backward
// compatibility by not firing the `playlistchange` event on the initial
// population of the list.
//
// @todo - Remove this condition in preparation for v5.
if (previousPlaylist) {
player.setTimeout(() => {
player.trigger({type: 'playlistchange', action: 'change'});
}, 0);
}
}
// Always return a shallow clone of the playlist list.
// We also want to return originalValue if any item in the list has it.
return list.map((item) => item.originalValue || item);
};
// On a new source, if there is no current item, disable auto-advance.
player.on('loadstart', () => {
if (playlist.currentItem() === -1) {
autoadvance.reset(player);
}
});
playlist.currentIndex_ = -1;
playlist.player_ = player;
playlist.autoadvance_ = {};
playlist.repeat_ = false;
playlist.currentPlaylistItemId_ = null;
/**
* Get or set the current item in the playlist.
*
* During the duringplaylistchange event, acts only as a getter.
*
* @param {number} [index]
* If given as a valid value, plays the playlist item at that index.
*
* @return {number}
* The current item index.
*/
playlist.currentItem = (index) => {
// If the playlist is changing, only act as a getter.
if (changing) {
return playlist.currentIndex_;
}
// Act as a setter when the index is given and is a valid number.
if (
typeof index === 'number' &&
playlist.currentIndex_ !== index &&
index >= 0 &&
index < list.length
) {
playlist.currentIndex_ = index;
playItem(
playlist.player_,
list[playlist.currentIndex_]
);
// When playing multiple videos in a playlist the videojs PosterImage
// will be hidden using CSS. However, in some browsers the native poster
// attribute will briefly appear while the new source loads. Prevent
// this by hiding every poster after the first play list item. This
// doesn't cover every use case for showing/hiding the poster, but
// it will significantly improve the user experience.
if (index > 0) {
player.poster('');
}
return playlist.currentIndex_;
}
const src = playlist.player_.currentSrc() || '';
// If there is a currentPlaylistItemId_, validate that it matches the
// current source URL returned by the player. This is sufficient evidence
// to suggest that the source was set by the playlist plugin. This code
// exists primarily to deal with playlists where multiple items have the
// same source.
if (playlist.currentPlaylistItemId_) {
const indexInItemIds = indexInPlaylistItemIds(list, playlist.currentPlaylistItemId_);
const item = list[indexInItemIds];
// Found a match, this is our current index!
if (item && Array.isArray(item.sources) && indexInSources([item], src) > -1) {
playlist.currentIndex_ = indexInItemIds;
return playlist.currentIndex_;
}
// If this does not match the current source, null it out so subsequent
// calls can skip this step.
playlist.currentPlaylistItemId_ = null;
}
// Finally, if we don't have a valid, current playlist item ID, we can
// auto-detect it based on the player's current source URL.
playlist.currentIndex_ = playlist.indexOf(src);
return playlist.currentIndex_;
};
/**
* A custom DOM event that is fired when new item(s) are added to the current
* playlist (rather than replacing the entire playlist).
*
* Unlike playlistchange, this is fired synchronously as it does not
* affect playback.
*
* @typedef {Object} PlaylistAddEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistadd"
*
* @property {number} count
* The number of items that were added.
*
* @property {number} index
* The starting index where item(s) were added.
*/
/**
* A custom DOM event that is fired when new item(s) are removed from the
* current playlist (rather than replacing the entire playlist).
*
* This is fired synchronously as it does not affect playback.
*
* @typedef {Object} PlaylistRemoveEvent
* @see [CustomEvent Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
* @property {string} type
* Always "playlistremove"
*
* @property {number} count
* The number of items that were removed.
*
* @property {number} index
* The starting index where item(s) were removed.
*/
/**
* Add one or more items to the playlist.
*
* @fires {PlaylistAddEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {string|Object|Array} item
* An item - or array of items - to be added to the playlist.
*
* @param {number} [index]
* If given as a valid value, injects the new playlist item(s)
* starting from that index. Otherwise, the item(s) are appended.
*/
playlist.add = (items, index) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
index = list.length;
}
if (!Array.isArray(items)) {
items = [items];
}
list.splice(index, 0, ...preparePlaylistItems(items));
// playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({type: 'playlistchange', action: 'add'});
player.trigger({type: 'playlistadd', count: items.length, index});
};
/**
* Remove one or more items from the playlist.
*
* @fires {PlaylistRemoveEvent}
* @throws {Error}
* If called during the duringplaylistchange event, throws an error.
*
* @param {number} index
* If a valid index in the current playlist, removes the item at that
* index from the playlist.
*
* If no valid index is given, nothing is removed from the playlist.
*
* @param {number} [count=1]
* The number of items to remove from the playlist.
*/
playlist.remove = (index, count = 1) => {
if (changing) {
throw new Error('cannot modify a playlist that is currently changing');
}
if (typeof index !== 'number' || index < 0 || index > list.length) {
return;
}
list.splice(index, count);
// playlistchange is triggered synchronously in this case because it does
// not change the current media source
player.trigger({type: 'playlistchange', action: 'remove'});
player.trigger({type: 'playlistremove', count, index});
};
/**
* Checks if the playlist contains a value.
*
* @param {string|Object|Array} value
* The value to check
*
* @return {boolean}
* The result
*/
playlist.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
*/
playlist.indexOf = (value) => {
if (typeof value === 'string') {
return indexInSources(list, value);
}
const sources = Array.isArray(value) ? value : value.sources;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (typeof source === 'string') {
return indexInSources(list, source);
} else if (source.src) {
return indexInSources(list, source.src);
}
}
return -1;
};
/**
* Get the index of the current item in the playlist. This is identical to
* calling `currentItem()` with no arguments.
*
* @return {number}
* The current item index.
*/
playlist.currentIndex = () => playlist.currentItem();
/**
* Get the index of the last item in the playlist.
*
* @return {number}
* The index of the last item in the playlist or -1 if there are no
* items.
*/
playlist.lastIndex = () => list.length - 1;
/**
* Get the index of the next item in the playlist.
*
* @return {number}
* The index of the next item in the playlist or -1 if there is no
* current item.
*/
playlist.nextIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
}
const lastIndex = playlist.lastIndex();
// When repeating, loop back to the beginning on the last item.
if (playlist.repeat_ && current === lastIndex) {
return 0;
}
// Don't go past the end of the playlist.
return Math.min(current + 1, lastIndex);
};
/**
* Get the index of the previous item in the playlist.
*
* @return {number}
* The index of the previous item in the playlist or -1 if there is
* no current item.
*/
playlist.previousIndex = () => {
const current = playlist.currentItem();
if (current === -1) {
return -1;
}
// When repeating, loop back to the end of the playlist.
if (playlist.repeat_ && current === 0) {
return playlist.lastIndex();
}
// Don't go past the beginning of the playlist.
return Math.max(current - 1, 0);
};
/**
* Plays the first item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if the list is empty.
*/
playlist.first = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(0);
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
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.
*/
playlist.last = () => {
if (changing) {
return;
}
const newItem = playlist.currentItem(playlist.lastIndex());
if (list.length) {
return list[newItem].originalValue || list[newItem];
}
playlist.currentIndex_ = -1;
};
/**
* Plays the next item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on last item.
*/
playlist.next = () => {
if (changing) {
return;
}
const index = playlist.nextIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Plays the previous item in the playlist.
*
* @return {Object|undefined}
* Returns undefined and has no side effects if on first item.
*/
playlist.previous = () => {
if (changing) {
return;
}
const index = playlist.previousIndex();
if (index !== playlist.currentIndex_) {
const newItem = playlist.currentItem(index);
return list[newItem].originalValue || list[newItem];
}
};
/**
* Set up auto-advance on the playlist.
*
* @param {number} [delay]
* The number of seconds to wait before each auto-advance.
*/
playlist.autoadvance = (delay) => {
autoadvance.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
*/
playlist.repeat = (val) => {
if (val === undefined) {
return playlist.repeat_;
}
if (typeof val !== 'boolean') {
videojs.log.error('videojs-playlist: Invalid value for repeat', val);
return;
}
playlist.repeat_ = !!val;
return playlist.repeat_;
};
/**
* Sorts the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
* @fires playlistsorted
*
* @param {Function} compare
* A comparator function as per the native Array method.
*/
playlist.sort = (compare) => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.sort(compare);
// If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Reverses the playlist array.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse}
* @fires playlistsorted
*/
playlist.reverse = () => {
// Bail if the array is empty.
if (!list.length) {
return;
}
list.reverse();
// If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
/**
* Shuffle the contents of the list randomly.
*
* @see {@link https://github.com/lodash/lodash/blob/40e096b6d5291a025e365a0f4c010d9a0efb9a69/shuffle.js}
* @fires playlistsorted
* @todo Make the `rest` option default to `true` in v5.0.0.
* @param {Object} [options]
* An object containing shuffle options.
*
* @param {boolean} [options.rest = false]
* By default, the entire playlist is randomized. However, this may
* not be desirable in all cases, such as when a user is already
* watching a video.
*
* When `true` is passed for this option, it will only shuffle
* playlist items after the current item. For example, when on the
* first item, will shuffle the second item and beyond.
*/
playlist.shuffle = ({rest} = {}) => {
let index = 0;
let arr = list;
// When options.rest is true, start randomization at the item after the
// current item.
if (rest) {
index = playlist.currentIndex_ + 1;
arr = list.slice(index);
}
// Bail if the array is empty or too short to shuffle.
if (arr.length <= 1) {
return;
}
randomize(arr);
// When options.rest is true, splice the randomized sub-array back into
// the original array.
if (rest) {
list.splice(...[index, arr.length].concat(arr));
}
// If the playlist is changing, don't trigger events.
if (changing) {
return;
}
/**
* Triggered after the playlist is sorted internally.
*
* @event playlistsorted
* @type {Object}
*/
player.trigger('playlistsorted');
};
// If an initial list was given, populate the playlist with it.
if (Array.isArray(initialList)) {
playlist(initialList, initialIndex);
// If there is no initial list given, silently set an empty array.
} else {
list = [];
}
return playlist;
}

26
node_modules/videojs-playlist/src/plugin.js generated vendored Normal file
View file

@ -0,0 +1,26 @@
import videojs from 'video.js';
import playlistMaker from './playlist-maker';
import {version as VERSION} from '../package.json';
// Video.js 5/6 cross-compatible.
const 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
*/
const plugin = function(list, item) {
playlistMaker(this, list, item);
};
registerPlugin('playlist', plugin);
plugin.VERSION = VERSION;
export default plugin;

167
node_modules/videojs-playlist/test/auto-advance.test.js generated vendored Normal file
View file

@ -0,0 +1,167 @@
import window from 'global/window';
import QUnit from 'qunit';
import sinon from 'sinon';
import * as autoadvance from '../src/auto-advance.js';
import playerProxyMaker from './player-proxy-maker.js';
QUnit.module('auto-advance');
QUnit.test('set up ended listener if one does not exist yet', function(assert) {
const player = playerProxyMaker();
const ones = [];
player.one = function(type) {
ones.push(type);
};
autoadvance.setup(player, 0);
assert.equal(ones.length, 1, 'there should have been only one one event added');
assert.equal(ones[0], 'ended', 'the event we want to one is "ended"');
});
QUnit.test('off previous listener if exists before adding a new one', function(assert) {
const player = playerProxyMaker();
const ones = [];
const offs = [];
player.one = function(type) {
ones.push(type);
};
player.off = function(type) {
offs.push(type);
};
autoadvance.setup(player, 0);
assert.equal(ones.length, 1, 'there should have been only one one event added');
assert.equal(ones[0], 'ended', 'the event we want to one is "ended"');
assert.equal(offs.length, 0, 'we should not have off-ed anything yet');
autoadvance.setup(player, 10);
assert.equal(ones.length, 2, 'there should have been only two one event added');
assert.equal(ones[0], 'ended', 'the event we want to one is "ended"');
assert.equal(ones[1], 'ended', 'the event we want to one is "ended"');
assert.equal(offs.length, 1, 'there should have been only one off event added');
assert.equal(offs[0], 'ended', 'the event we want to off is "ended"');
});
QUnit.test('do nothing if timeout is weird', function(assert) {
const player = playerProxyMaker();
const ones = [];
const offs = [];
player.one = function(type) {
ones.push(type);
};
player.off = function(type) {
offs.push(type);
};
autoadvance.setup(player, -1);
autoadvance.setup(player, -100);
autoadvance.setup(player, null);
autoadvance.setup(player, {});
autoadvance.setup(player, []);
assert.equal(offs.length, 0, 'we did nothing');
assert.equal(ones.length, 0, 'we did nothing');
});
QUnit.test('reset if timeout is weird after we advance', function(assert) {
const player = playerProxyMaker();
const ones = [];
const offs = [];
player.one = function(type) {
ones.push(type);
};
player.off = function(type) {
offs.push(type);
};
autoadvance.setup(player, 0);
autoadvance.setup(player, -1);
autoadvance.setup(player, 0);
autoadvance.setup(player, -100);
autoadvance.setup(player, 0);
autoadvance.setup(player, null);
autoadvance.setup(player, 0);
autoadvance.setup(player, {});
autoadvance.setup(player, 0);
autoadvance.setup(player, []);
autoadvance.setup(player, 0);
autoadvance.setup(player, NaN);
autoadvance.setup(player, 0);
autoadvance.setup(player, Infinity);
autoadvance.setup(player, 0);
autoadvance.setup(player, -Infinity);
assert.equal(offs.length, 8, 'we reset the advance 8 times');
assert.equal(ones.length, 8, 'we autoadvanced 8 times');
});
QUnit.test('reset if we have already started advancing', function(assert) {
const player = playerProxyMaker();
const oldClearTimeout = window.clearTimeout;
let clears = 0;
window.clearTimeout = function() {
clears++;
};
// pretend we started autoadvancing
player.playlist.autoadvance_.timeout = 1;
autoadvance.setup(player, 0);
assert.equal(clears, 1, 'we reset the auto advance');
window.clearTimeout = oldClearTimeout;
});
QUnit.test('timeout is given in seconds', function(assert) {
const player = playerProxyMaker();
const oldSetTimeout = window.setTimeout;
player.addEventListener = Function.prototype;
window.setTimeout = function(fn, timeout) {
assert.equal(timeout, 10 * 1000, 'timeout was given in seconds');
};
autoadvance.setup(player, 10);
player.trigger('ended');
window.setTimeout = oldSetTimeout;
});
QUnit.test('cancel a pending auto-advance if play is requested', function(assert) {
const clock = sinon.useFakeTimers();
const player = playerProxyMaker();
sinon.spy(player.playlist, 'next');
autoadvance.setup(player, 10);
player.trigger('ended');
clock.tick(10000);
assert.equal(player.playlist.next.callCount, 1, 'next was called');
autoadvance.setup(player, 10);
player.trigger('ended');
clock.tick(5000);
player.trigger('play');
clock.tick(5000);
assert.equal(player.playlist.next.callCount, 1, 'next was not called because a "play" occurred');
player.trigger('ended');
clock.tick(10000);
assert.equal(player.playlist.next.callCount, 2, 'next was called again because the content ended again and the appropriate wait time elapsed');
});

187
node_modules/videojs-playlist/test/current-item.test.js generated vendored Normal file
View file

@ -0,0 +1,187 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import '../src/plugin';
import {createFixturePlayer, destroyFixturePlayer} from './util';
const samplePlaylist = [{
sources: [{
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/sintel/poster.png'
}, {
sources: [{
src: 'http://media.w3.org/2010/05/bunny/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/bunny/poster.png'
}, {
sources: [{
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
}],
poster: 'http://www.videojs.com/img/poster.jpg'
}];
QUnit.module('current-item', {
beforeEach() {
this.clock = sinon.useFakeTimers();
createFixturePlayer(this);
},
afterEach() {
destroyFixturePlayer(this);
this.clock.restore();
}
}, function() {
QUnit.module('without a playlist', function() {
QUnit.test('player without a source', function(assert) {
assert.strictEqual(this.player.playlist.currentItem(), -1, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), -1, 'currentItem() after tech ready');
});
QUnit.test('player with a source', function(assert) {
assert.strictEqual(this.player.playlist.currentItem(), -1, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
});
assert.strictEqual(this.player.playlist.currentItem(), -1, 'currentItem() after tech ready');
});
});
QUnit.module('with a playlist', function() {
QUnit.test('set new source by calling currentItem()', function(assert) {
this.player.playlist(samplePlaylist);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() after tech ready');
this.player.playlist.currentItem(1);
assert.strictEqual(this.player.playlist.currentItem(), 1, 'currentItem() changes the current item');
});
QUnit.test('set a new source via src()', function(assert) {
this.player.playlist(samplePlaylist);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() after tech ready');
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
});
assert.strictEqual(this.player.playlist.currentItem(), 2, 'src() changes the current item');
});
QUnit.test('set a new source via src() - source is NOT in the playlist', function(assert) {
// Populate the player with a playlist without oceans.mp4
this.player.playlist(samplePlaylist.slice(0, 2));
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() after tech ready');
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
});
assert.strictEqual(this.player.playlist.currentItem(), -1, 'src() changes the current item');
});
});
QUnit.module('duplicate sources playlist', function() {
QUnit.test('set new sources by calling currentItem()', function(assert) {
// Populate the player with a playlist with another sintel on the end.
this.player.playlist(samplePlaylist.concat([{
sources: [{
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/sintel/poster.png'
}]));
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() after tech ready');
// Set the playlist to the last item.
this.player.playlist.currentItem(3);
assert.strictEqual(this.player.playlist.currentItem(), 3, 'currentItem() matches the duplicated item that was actually selected');
// Set the playlist back to the first item (also sintel).
this.player.playlist.currentItem(0);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() matches the duplicated item that was actually selected');
// Set the playlist to the second item (NOT sintel).
this.player.playlist.currentItem(1);
assert.strictEqual(this.player.playlist.currentItem(), 1, 'currentItem() is correct');
});
QUnit.test('set new source by calling src()', function(assert) {
// Populate the player with a playlist with another sintel on the end.
this.player.playlist(samplePlaylist.concat([{
sources: [{
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
}],
poster: 'http://media.w3.org/2010/05/sintel/poster.png'
}]));
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() before tech ready');
// Tick forward to ready the playback tech.
this.clock.tick(1);
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() after tech ready');
// Set the playlist to the second item (NOT sintel).
this.player.playlist.currentItem(1);
assert.strictEqual(this.player.playlist.currentItem(), 1, 'currentItem() acted as a setter');
this.player.src({
src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
type: 'video/mp4'
});
assert.strictEqual(this.player.playlist.currentItem(), 0, 'currentItem() defaults to the first playlist item that matches the current source');
});
});
});

9885
node_modules/videojs-playlist/test/dist/bundle.js generated vendored Normal file

File diff suppressed because it is too large Load diff

137
node_modules/videojs-playlist/test/play-item.test.js generated vendored Normal file
View file

@ -0,0 +1,137 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import playItem from '../src/play-item';
import {clearTracks} from '../src/play-item';
import playerProxyMaker from './player-proxy-maker';
QUnit.module('play-item');
QUnit.test('clearTracks will try and remove all tracks', function(assert) {
const player = playerProxyMaker();
const remoteTracks = [1, 2, 3];
const removedTracks = [];
player.remoteTextTracks = function() {
return remoteTracks;
};
player.removeRemoteTextTrack = function(tt) {
removedTracks.push(tt);
};
clearTracks(player);
assert.deepEqual(
removedTracks.sort(),
remoteTracks.sort(),
'the removed tracks are equivalent to our remote tracks'
);
});
QUnit.test('will not try to play if paused', function(assert) {
const player = playerProxyMaker();
let tryPlay = false;
player.paused = function() {
return true;
};
player.play = function() {
tryPlay = true;
};
playItem(player, {
sources: [1, 2, 3],
textTracks: [4, 5, 6],
poster: 'http://example.com/poster.png'
});
assert.ok(!tryPlay, 'we did not reply on paused');
});
QUnit.test('will try to play if not paused', function(assert) {
const player = playerProxyMaker();
let tryPlay = false;
player.paused = function() {
return false;
};
player.play = function() {
tryPlay = true;
};
playItem(player, {
sources: [1, 2, 3],
textTracks: [4, 5, 6],
poster: 'http://example.com/poster.png'
});
assert.ok(tryPlay, 'we replayed on not-paused');
});
QUnit.test('will not try to play if paused and not ended', function(assert) {
const player = playerProxyMaker();
let tryPlay = false;
player.paused = function() {
return true;
};
player.ended = function() {
return false;
};
player.play = function() {
tryPlay = true;
};
playItem(player, {
sources: [1, 2, 3],
textTracks: [4, 5, 6],
poster: 'http://example.com/poster.png'
});
assert.ok(!tryPlay, 'we did not replaye on paused and not ended');
});
QUnit.test('will try to play if paused and ended', function(assert) {
const player = playerProxyMaker();
let tryPlay = false;
player.paused = function() {
return true;
};
player.ended = function() {
return true;
};
player.play = function() {
tryPlay = true;
};
playItem(player, {
sources: [1, 2, 3],
poster: 'http://example.com/poster.png'
});
assert.ok(tryPlay, 'we replayed on not-paused');
});
QUnit.test('fires "beforeplaylistitem" and "playlistitem"', function(assert) {
const player = playerProxyMaker();
const beforeSpy = sinon.spy();
const spy = sinon.spy();
player.on('beforeplaylistitem', beforeSpy);
player.on('playlistitem', spy);
playItem(player, {
sources: [1, 2, 3],
poster: 'http://example.com/poster.png'
});
assert.strictEqual(beforeSpy.callCount, 1);
assert.strictEqual(spy.callCount, 1);
});

View file

@ -0,0 +1,42 @@
import window from 'global/window';
import videojs from 'video.js';
const proxy = (props) => {
let poster_ = '';
const player = Object.assign({}, videojs.EventTarget.prototype, {
play: () => {},
paused: () => {},
ended: () => {},
poster: (src) => {
if (src !== undefined) {
poster_ = src;
} return poster_;
},
src: () => {},
currentSrc: () => {},
addRemoteTextTrack: () => {},
removeRemoteTextTrack: () => {},
remoteTextTracks: () => {},
playlist: () => [],
ready: (cb) => cb(),
setTimeout: (cb, wait) => window.setTimeout(cb, wait),
clearTimeout: (id) => window.clearTimeout(id)
}, props);
player.constructor = videojs.getComponent('Player');
player.playlist.player_ = player;
player.playlist.autoadvance_ = {};
player.playlist.currentIndex_ = -1;
player.playlist.autoadvance = () => {};
player.playlist.contains = () => {};
player.playlist.currentItem = () => {};
player.playlist.first = () => {};
player.playlist.indexOf = () => {};
player.playlist.next = () => {};
player.playlist.previous = () => {};
return player;
};
export default proxy;

1067
node_modules/videojs-playlist/test/playlist-maker.test.js generated vendored Normal file

File diff suppressed because it is too large Load diff

20
node_modules/videojs-playlist/test/plugin.test.js generated vendored Normal file
View file

@ -0,0 +1,20 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';
import plugin from '../src/plugin';
QUnit.test('the environment is sane', function(assert) {
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
assert.strictEqual(typeof sinon, 'object', 'sinon exists');
assert.strictEqual(typeof videojs, 'function', 'videojs exists');
assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
});
QUnit.test('registers itself with video.js', function(assert) {
assert.expect(1);
assert.strictEqual(
typeof videojs.getComponent('Player').prototype.playlist,
'function',
'videojs-playlist plugin was registered'
);
});

31
node_modules/videojs-playlist/test/util.js generated vendored Normal file
View file

@ -0,0 +1,31 @@
import document from 'global/document';
import videojs from 'video.js';
/**
* Destroy a fixture player.
*
* @param {Object} context
* A testing context.
*/
export function destroyFixturePlayer(context) {
context.player.dispose();
}
/**
* Create a fixture player.
*
* @param {Object} context
* A testing context.
*/
export function createFixturePlayer(context) {
context.video = document.createElement('video');
context.fixture = document.querySelector('#qunit-fixture');
context.fixture.appendChild(context.video);
context.playerIsReady = false;
context.player = videojs(context.video, {}, () => {
context.playerIsReady = true;
});
context.player.playlist();
}

43
package-lock.json generated
View file

@ -38,6 +38,8 @@
"videojs-font": "^4.1.0", "videojs-font": "^4.1.0",
"videojs-ima": "^2.1.0", "videojs-ima": "^2.1.0",
"videojs-landscape-fullscreen": "^11.1111.0", "videojs-landscape-fullscreen": "^11.1111.0",
"videojs-playlist": "^5.1.0",
"videojs-playlist-ui": "^5.0.0",
"videojs-seek-buttons": "^4.0.3" "videojs-seek-buttons": "^4.0.3"
} }
}, },
@ -2062,6 +2064,30 @@
"video.js": "5.x || 6.x || 7.x || *" "video.js": "5.x || 6.x || 7.x || *"
} }
}, },
"node_modules/videojs-playlist": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.0.tgz",
"integrity": "sha512-p5ohld6Kom9meYCcEVYj0JVS2MBL2XxMiU+IDB/xKpDOspFAHrERHrZEBoiJZc/mCfHixZBNgj1vWRgYsVVsrw==",
"dependencies": {
"global": "^4.3.2",
"video.js": "^6 || ^7 || ^8"
},
"engines": {
"node": ">=4.4.0"
}
},
"node_modules/videojs-playlist-ui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/videojs-playlist-ui/-/videojs-playlist-ui-5.0.0.tgz",
"integrity": "sha512-Ct6uzWO7FittnXqWU0mnF+ahE1IYvXYVTu5O1qGPW5BkUdW+j0b+wwbiQp3Wj49MXB2A/gKfgIpm4QFVRKq8kw==",
"dependencies": {
"global": "^4.4.0"
},
"peerDependencies": {
"video.js": "^8.0.0",
"videojs-playlist": "^5.1.0"
}
},
"node_modules/videojs-seek-buttons": { "node_modules/videojs-seek-buttons": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz", "resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz",
@ -3697,6 +3723,23 @@
"global": "^4.4.0" "global": "^4.4.0"
} }
}, },
"videojs-playlist": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.0.tgz",
"integrity": "sha512-p5ohld6Kom9meYCcEVYj0JVS2MBL2XxMiU+IDB/xKpDOspFAHrERHrZEBoiJZc/mCfHixZBNgj1vWRgYsVVsrw==",
"requires": {
"global": "^4.3.2",
"video.js": "^6 || ^7 || ^8"
}
},
"videojs-playlist-ui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/videojs-playlist-ui/-/videojs-playlist-ui-5.0.0.tgz",
"integrity": "sha512-Ct6uzWO7FittnXqWU0mnF+ahE1IYvXYVTu5O1qGPW5BkUdW+j0b+wwbiQp3Wj49MXB2A/gKfgIpm4QFVRKq8kw==",
"requires": {
"global": "^4.4.0"
}
},
"videojs-seek-buttons": { "videojs-seek-buttons": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz", "resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz",

View file

@ -33,6 +33,8 @@
"videojs-font": "^4.1.0", "videojs-font": "^4.1.0",
"videojs-ima": "^2.1.0", "videojs-ima": "^2.1.0",
"videojs-landscape-fullscreen": "^11.1111.0", "videojs-landscape-fullscreen": "^11.1111.0",
"videojs-playlist": "^5.1.0",
"videojs-playlist-ui": "^5.0.0",
"videojs-seek-buttons": "^4.0.3" "videojs-seek-buttons": "^4.0.3"
} }
} }