mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 01:39:24 +02:00
update
This commit is contained in:
parent
ed9048ec37
commit
14f492d68f
60 changed files with 27146 additions and 0 deletions
24
node_modules/.package-lock.json
generated
vendored
24
node_modules/.package-lock.json
generated
vendored
|
@ -2024,6 +2024,30 @@
|
|||
"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": {
|
||||
"version": "4.0.3",
|
||||
"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
30
node_modules/videojs-playlist-ui/CONTRIBUTING.md
generated
vendored
Normal 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
13
node_modules/videojs-playlist-ui/LICENSE
generated
vendored
Normal 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
109
node_modules/videojs-playlist-ui/README.md
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
# videojs-playlist-ui
|
||||
|
||||
[](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
5
node_modules/videojs-playlist-ui/dist/lang/ar.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/de.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/en.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/es.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/fr.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/ja.js
generated
vendored
Normal 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
5
node_modules/videojs-playlist-ui/dist/lang/ko.js
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
videojs.addLanguage('ko', {
|
||||
"Now Playing": "지금 재생 중",
|
||||
"Up Next": "다음",
|
||||
"Untitled Video": "제목 없는 비디오"
|
||||
});
|
5
node_modules/videojs-playlist-ui/dist/lang/zh-Hans.js
generated
vendored
Normal file
5
node_modules/videojs-playlist-ui/dist/lang/zh-Hans.js
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
videojs.addLanguage('zh-Hans', {
|
||||
"Now Playing": "正在播放",
|
||||
"Up Next": "播放下一个",
|
||||
"Untitled Video": "无标题视频"
|
||||
});
|
5
node_modules/videojs-playlist-ui/dist/lang/zh-Hant.js
generated
vendored
Normal file
5
node_modules/videojs-playlist-ui/dist/lang/zh-Hant.js
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
videojs.addLanguage('zh-Hant', {
|
||||
"Now Playing": "正在播放",
|
||||
"Up Next": "播放下一個",
|
||||
"Untitled Video": "未命名視訊"
|
||||
});
|
467
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.cjs.js
generated
vendored
Normal file
467
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.cjs.js
generated
vendored
Normal 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;
|
1
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.css
generated
vendored
Normal file
1
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.css
generated
vendored
Normal 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 */
|
1
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.css.map
generated
vendored
Normal file
1
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.css.map
generated
vendored
Normal 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"}
|
460
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.es.js
generated
vendored
Normal file
460
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.es.js
generated
vendored
Normal 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 };
|
469
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.js
generated
vendored
Normal file
469
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.js
generated
vendored
Normal 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;
|
||||
|
||||
}));
|
2
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.min.js
generated
vendored
Normal file
2
node_modules/videojs-playlist-ui/dist/videojs-playlist-ui.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
18
node_modules/videojs-playlist-ui/index.html
generated
vendored
Normal file
18
node_modules/videojs-playlist-ui/index.html
generated
vendored
Normal 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
105
node_modules/videojs-playlist-ui/package.json
generated
vendored
Normal 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
13
node_modules/videojs-playlist-ui/scripts/karma.conf.js
generated
vendored
Normal 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!
|
||||
};
|
||||
|
9
node_modules/videojs-playlist-ui/scripts/postcss.config.js
generated
vendored
Normal file
9
node_modules/videojs-playlist-ui/scripts/postcss.config.js
generated
vendored
Normal 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;
|
||||
};
|
11
node_modules/videojs-playlist-ui/scripts/rollup.config.js
generated
vendored
Normal file
11
node_modules/videojs-playlist-ui/scripts/rollup.config.js
generated
vendored
Normal 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);
|
177
node_modules/videojs-playlist-ui/src/playlist-menu-item.js
generated
vendored
Normal file
177
node_modules/videojs-playlist-ui/src/playlist-menu-item.js
generated
vendored
Normal 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
252
node_modules/videojs-playlist-ui/src/playlist-menu.js
generated
vendored
Normal 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
112
node_modules/videojs-playlist-ui/src/plugin.js
generated
vendored
Normal 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
269
node_modules/videojs-playlist-ui/src/plugin.scss
generated
vendored
Normal 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
6966
node_modules/videojs-playlist-ui/test/dist/bundle.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
BIN
node_modules/videojs-playlist-ui/test/example/oceans-low.jpg
generated
vendored
Normal file
BIN
node_modules/videojs-playlist-ui/test/example/oceans-low.jpg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
node_modules/videojs-playlist-ui/test/example/oceans.jpg
generated
vendored
Normal file
BIN
node_modules/videojs-playlist-ui/test/example/oceans.jpg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
869
node_modules/videojs-playlist-ui/test/plugin.test.js
generated
vendored
Normal file
869
node_modules/videojs-playlist-ui/test/plugin.test.js
generated
vendored
Normal 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
209
node_modules/videojs-playlist/CHANGELOG.md
generated
vendored
Normal 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
30
node_modules/videojs-playlist/CONTRIBUTING.md
generated
vendored
Normal 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
13
node_modules/videojs-playlist/LICENSE
generated
vendored
Normal 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
96
node_modules/videojs-playlist/README.md
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
# videojs-playlist
|
||||
|
||||
[](https://travis-ci.org/brightcove/videojs-playlist)
|
||||
[](https://greenkeeper.io/)
|
||||
[](http://slack.videojs.com)
|
||||
|
||||
[](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.
|
988
node_modules/videojs-playlist/dist/videojs-playlist.cjs.js
generated
vendored
Normal file
988
node_modules/videojs-playlist/dist/videojs-playlist.cjs.js
generated
vendored
Normal 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;
|
982
node_modules/videojs-playlist/dist/videojs-playlist.es.js
generated
vendored
Normal file
982
node_modules/videojs-playlist/dist/videojs-playlist.es.js
generated
vendored
Normal 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
992
node_modules/videojs-playlist/dist/videojs-playlist.js
generated
vendored
Normal 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;
|
||||
|
||||
}));
|
2
node_modules/videojs-playlist/dist/videojs-playlist.min.js
generated
vendored
Normal file
2
node_modules/videojs-playlist/dist/videojs-playlist.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
599
node_modules/videojs-playlist/docs/api.md
generated
vendored
Normal file
599
node_modules/videojs-playlist/docs/api.md
generated
vendored
Normal 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
1
node_modules/videojs-playlist/docs/index.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# Documentation for videojs-playlist
|
114
node_modules/videojs-playlist/index.html
generated
vendored
Normal file
114
node_modules/videojs-playlist/index.html
generated
vendored
Normal 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
98
node_modules/videojs-playlist/package.json
generated
vendored
Normal 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
13
node_modules/videojs-playlist/scripts/karma.conf.js
generated
vendored
Normal 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
11
node_modules/videojs-playlist/scripts/rollup.config.js
generated
vendored
Normal 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
97
node_modules/videojs-playlist/src/auto-advance.js
generated
vendored
Normal 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
67
node_modules/videojs-playlist/src/play-item.js
generated
vendored
Normal 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
805
node_modules/videojs-playlist/src/playlist-maker.js
generated
vendored
Normal 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
26
node_modules/videojs-playlist/src/plugin.js
generated
vendored
Normal 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
167
node_modules/videojs-playlist/test/auto-advance.test.js
generated
vendored
Normal 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
187
node_modules/videojs-playlist/test/current-item.test.js
generated
vendored
Normal 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
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
137
node_modules/videojs-playlist/test/play-item.test.js
generated
vendored
Normal 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);
|
||||
});
|
42
node_modules/videojs-playlist/test/player-proxy-maker.js
generated
vendored
Normal file
42
node_modules/videojs-playlist/test/player-proxy-maker.js
generated
vendored
Normal 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
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
20
node_modules/videojs-playlist/test/plugin.test.js
generated
vendored
Normal 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
31
node_modules/videojs-playlist/test/util.js
generated
vendored
Normal 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
43
package-lock.json
generated
|
@ -38,6 +38,8 @@
|
|||
"videojs-font": "^4.1.0",
|
||||
"videojs-ima": "^2.1.0",
|
||||
"videojs-landscape-fullscreen": "^11.1111.0",
|
||||
"videojs-playlist": "^5.1.0",
|
||||
"videojs-playlist-ui": "^5.0.0",
|
||||
"videojs-seek-buttons": "^4.0.3"
|
||||
}
|
||||
},
|
||||
|
@ -2062,6 +2064,30 @@
|
|||
"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": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz",
|
||||
|
@ -3697,6 +3723,23 @@
|
|||
"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": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-4.0.3.tgz",
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
"videojs-font": "^4.1.0",
|
||||
"videojs-ima": "^2.1.0",
|
||||
"videojs-landscape-fullscreen": "^11.1111.0",
|
||||
"videojs-playlist": "^5.1.0",
|
||||
"videojs-playlist-ui": "^5.0.0",
|
||||
"videojs-seek-buttons": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue