mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-05 19:42:38 +02:00
update
This commit is contained in:
parent
ed9048ec37
commit
14f492d68f
60 changed files with 27146 additions and 0 deletions
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');
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue