mirror of
https://gitea.invidious.io/iv-org/videojs-vtt-thumbnails.git
synced 2024-08-15 00:43:16 +00:00
Initial commit
This commit is contained in:
commit
f0a97f8e13
23 changed files with 1020 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# OS
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
._*
|
||||
|
||||
# Editors
|
||||
*~
|
||||
*.swp
|
||||
*.tmproj
|
||||
*.tmproject
|
||||
*.sublime-*
|
||||
.idea/
|
||||
.project/
|
||||
.settings/
|
||||
.vscode/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
bower_components/
|
||||
node_modules/
|
||||
|
||||
# Yeoman meta-data
|
||||
.yo-rc.json
|
||||
|
||||
# Build-related directories
|
||||
dist/
|
||||
docs/api/
|
||||
test/dist/
|
3
.npmignore
Normal file
3
.npmignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Intentionally left blank, so that npm does not ignore anything by default,
|
||||
# but relies on the package.json "files" array to explicitly define what ends
|
||||
# up in the package.
|
32
.travis.yml
Normal file
32
.travis.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
sudo: false
|
||||
dist: trusty
|
||||
language: node_js
|
||||
node_js:
|
||||
- 'node'
|
||||
- 'lts/argon'
|
||||
before_script:
|
||||
|
||||
# Check if the current version is equal to the major version for the env.
|
||||
- 'export IS_INSTALLED="$(npm list video.js | grep "video.js@$VJS")"'
|
||||
|
||||
# We have to add semicolons to the end of each line in the if as Travis runs
|
||||
# this all on one line.
|
||||
- 'if [ -z "$IS_INSTALLED" ]; then
|
||||
echo "INSTALLING video.js@>=$VJS.0.0-RC.0 <$(($VJS+1)).0.0";
|
||||
npm i "video.js@>=$VJS.0.0-RC.0 <\$(($VJS+1)).0.0";
|
||||
else
|
||||
echo "video.js@$VJS ALREADY INSTALLED";
|
||||
fi'
|
||||
- export CHROME_BIN=/usr/bin/google-chrome
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
env:
|
||||
- VJS=5
|
||||
- VJS=6
|
||||
addons:
|
||||
firefox: latest
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# CONTRIBUTING
|
||||
|
||||
We welcome contributions from everyone!
|
||||
|
||||
## Getting Started
|
||||
|
||||
Make sure you have NodeJS 0.10 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
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) Chris Boustead <chris@forgemotion.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
80
README.md
Normal file
80
README.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# videojs-vtt-thumbnails
|
||||
|
||||
Video.js plugin that displays thumbnails on progress bar hover, driven by external VTT files. Based on the spec at: https://support.jwplayer.com/customer/portal/articles/1407439-adding-preview-thumbnails
|
||||
|
||||
Note: Plugin hides the default skin's mouse display timestamp on hover.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Installation
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [`<script>` Tag](#script-tag)
|
||||
- [Browserify/CommonJS](#browserifycommonjs)
|
||||
- [RequireJS/AMD](#requirejsamd)
|
||||
- [License](#license)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install --save videojs-vtt-thumbnails
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To include videojs-vtt-thumbnails on your website or web application, use any of the following methods.
|
||||
|
||||
### `<script>` Tag
|
||||
|
||||
This is the simplest case. Get the script in whatever way you prefer and include the plugin _after_ you include [video.js][videojs], so that the `videojs` global is available.
|
||||
|
||||
```html
|
||||
<script src="//path/to/video.min.js"></script>
|
||||
<script src="//path/to/videojs-vtt-thumbnails.min.js"></script>
|
||||
<script>
|
||||
var player = videojs('my-video');
|
||||
player.vttThumbnails({
|
||||
src: 'example/thumbs.vtt'
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Browserify/CommonJS
|
||||
|
||||
When using with Browserify, install videojs-vtt-thumbnails via npm and `require` the plugin as you would any other module.
|
||||
|
||||
```js
|
||||
var videojs = require('video.js');
|
||||
|
||||
// The actual plugin function is exported by this module, but it is also
|
||||
// attached to the `Player.prototype`; so, there is no need to assign it
|
||||
// to a variable.
|
||||
require('videojs-vtt-thumbnails');
|
||||
|
||||
var player = videojs('my-video');
|
||||
|
||||
player.vttThumbnails();
|
||||
```
|
||||
|
||||
### RequireJS/AMD
|
||||
|
||||
When using with RequireJS (or another AMD library), get the script in whatever way you prefer and `require` the plugin as you normally would:
|
||||
|
||||
```js
|
||||
require(['video.js', 'videojs-vtt-thumbnails'], function(videojs) {
|
||||
var player = videojs('my-video');
|
||||
|
||||
player.vttThumbnails();
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT. Copyright (c) Chris Boustead <chris@forgemotion.com>
|
||||
|
||||
|
||||
[videojs]: http://videojs.com/
|
BIN
example/thumbs.jpg
Normal file
BIN
example/thumbs.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
13
example/thumbs.vtt
Normal file
13
example/thumbs.vtt
Normal file
|
@ -0,0 +1,13 @@
|
|||
WEBVTT
|
||||
|
||||
00:00.000 --> 00:05.000
|
||||
thumbs.jpg#xywh=0,0,160,70
|
||||
|
||||
00:05.000 --> 00:10.000
|
||||
thumbs.jpg#xywh=160,0,160,70
|
||||
|
||||
00:10.000 --> 00:15.000
|
||||
thumbs.jpg#xywh=0,70,160,70
|
||||
|
||||
00:15.000 --> 00:20.000
|
||||
thumbs.jpg#xywh=160,70,160,70
|
30
index.html
Normal file
30
index.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>videojs-vtt-thumbnails Demo</title>
|
||||
<link href="node_modules/video.js/dist/video-js.css" rel="stylesheet">
|
||||
<link href="dist/videojs-vtt-thumbnails.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<video id="videojs-vtt-thumbnails-player" class="video-js vjs-default-skin" controls>
|
||||
<source src="//vjs.zencdn.net/v/oceans.mp4" type='video/mp4'>
|
||||
<source src="//vjs.zencdn.net/v/oceans.webm" type='video/webm'>
|
||||
</video>
|
||||
<ul>
|
||||
<li><a href="test/">Run unit tests in browser.</a></li>
|
||||
<li><a href="docs/api/">Read generated docs.</a></li>
|
||||
</ul>
|
||||
|
||||
<script src="node_modules/video.js/dist/video.js"></script>
|
||||
<script src="dist/videojs-vtt-thumbnails.js"></script>
|
||||
<script>
|
||||
(function(window, videojs) {
|
||||
var player = window.player = videojs('videojs-vtt-thumbnails-player');
|
||||
player.vttThumbnails({
|
||||
src: 'example/thumbs.vtt'
|
||||
});
|
||||
}(window, window.videojs));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3
jsdoc.json
Normal file
3
jsdoc.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"plugins": ["plugins/markdown"]
|
||||
}
|
107
package.json
Normal file
107
package.json
Normal file
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"name": "videojs-vtt-thumbnails",
|
||||
"version": "0.0.0",
|
||||
"description": "Display thumnails on progress bar hover, driven by external VTT files.",
|
||||
"main": "dist/videojs-vtt-thumbnails.cjs.js",
|
||||
"module": "dist/videojs-vtt-thumbnails.es.js",
|
||||
"generator-videojs-plugin": {
|
||||
"version": "5.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"build": "npm-run-all -p build:*",
|
||||
"build:css": "npm-run-all build:css:sass build:css:bannerize",
|
||||
"build:css:bannerize": "bannerize dist/videojs-vtt-thumbnails.css --banner=scripts/banner.ejs",
|
||||
"build:css:sass": "node-sass src/plugin.scss dist/videojs-vtt-thumbnails.css --output-style=compressed --linefeed=lf",
|
||||
"build:js": "npm-run-all build:js:rollup-modules build:js:rollup-umd build:js:bannerize build:js:uglify",
|
||||
"build:js:bannerize": "bannerize dist/videojs-vtt-thumbnails.js --banner=scripts/banner.ejs",
|
||||
"build:js:rollup-modules": "rollup -c scripts/modules.rollup.config.js",
|
||||
"build:js:rollup-umd": "rollup -c scripts/umd.rollup.config.js",
|
||||
"build:js:uglify": "uglifyjs dist/videojs-vtt-thumbnails.js --comments --mangle --compress -o dist/videojs-vtt-thumbnails.min.js",
|
||||
"build:test": "rollup -c scripts/test.rollup.config.js",
|
||||
"clean": "rimraf dist test/dist",
|
||||
"postclean": "mkdirp dist test/dist",
|
||||
"docs": "npm-run-all docs:*",
|
||||
"docs:api": "jsdoc src -r -c jsdoc.json -d docs/api",
|
||||
"docs:toc": "doctoc README.md",
|
||||
"lint": "vjsstandard",
|
||||
"start": "npm-run-all -p start:server watch",
|
||||
"start:server": "static -a 0.0.0.0 -p 9999 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}' .",
|
||||
"pretest": "npm-run-all lint build",
|
||||
"test": "karma start test/karma.conf.js",
|
||||
"preversion": "npm test",
|
||||
"version": "node scripts/version.js",
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
"watch:css": "npm-run-all build:css:sass watch:css:sass",
|
||||
"watch:css:sass": "node-sass src/plugin.scss dist/videojs-vtt-thumbnails.css --output-style=compressed --linefeed=lf --watch src/**/*.scss",
|
||||
"watch:js-modules": "rollup -c scripts/modules.rollup.config.js -w",
|
||||
"watch:js-umd": "rollup -c scripts/umd.rollup.config.js -w",
|
||||
"watch:test": "rollup -c scripts/test.rollup.config.js -w",
|
||||
"prepublish": "npm run build",
|
||||
"prepush": "npm run lint",
|
||||
"precommit": "npm run docs:toc && git add README.md"
|
||||
},
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"videojs-plugin"
|
||||
],
|
||||
"author": "Chris Boustead <chris@forgemotion.com>",
|
||||
"license": "MIT",
|
||||
"vjsstandard": {
|
||||
"ignore": [
|
||||
"dist",
|
||||
"docs",
|
||||
"test/dist",
|
||||
"test/karma.conf.js"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"CONTRIBUTING.md",
|
||||
"dist/",
|
||||
"docs/",
|
||||
"index.html",
|
||||
"scripts/",
|
||||
"src/",
|
||||
"test/"
|
||||
],
|
||||
"dependencies": {
|
||||
"global": "^4.3.2",
|
||||
"request": "^2.83.0",
|
||||
"video.js": "^5.19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-transform-object-assign": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"bannerize": "^1.0.2",
|
||||
"conventional-changelog-cli": "^1.3.1",
|
||||
"conventional-changelog-videojs": "^3.0.0",
|
||||
"doctoc": "^1.3.0",
|
||||
"husky": "^0.13.3",
|
||||
"jsdoc": "^3.4.3",
|
||||
"karma": "^1.7.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
"karma-detect-browsers": "^2.2.5",
|
||||
"karma-firefox-launcher": "^1.0.1",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-qunit": "^1.2.1",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-sass": "4.5.3",
|
||||
"node-static": "^0.7.9",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"qunitjs": "^2.3.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.50.0",
|
||||
"rollup-plugin-babel": "^2.7.1",
|
||||
"rollup-plugin-commonjs": "^8.0.2",
|
||||
"rollup-plugin-json": "^2.1.1",
|
||||
"rollup-plugin-multi-entry": "^2.0.1",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"rollup-watch": "^3.2.2",
|
||||
"semver": "^5.3.0",
|
||||
"sinon": "^2.2.0",
|
||||
"uglify-js": "^3.0.7",
|
||||
"videojs-standard": "^6.0.0"
|
||||
}
|
||||
}
|
6
scripts/banner.ejs
Normal file
6
scripts/banner.ejs
Normal file
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* <%- pkg.name %>
|
||||
* @version <%- pkg.version %>
|
||||
* @copyright <%- date.getFullYear() %> <%- pkg.author %>
|
||||
* @license <%- pkg.license %>
|
||||
*/
|
47
scripts/modules.rollup.config.js
Normal file
47
scripts/modules.rollup.config.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Rollup configuration for packaging the plugin in a module that is consumable
|
||||
* by either CommonJS (e.g. Node or Browserify) or ECMAScript (e.g. Rollup).
|
||||
*
|
||||
* These modules DO NOT include their dependencies as we expect those to be
|
||||
* handled by the module system.
|
||||
*/
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import json from 'rollup-plugin-json';
|
||||
|
||||
export default {
|
||||
name: 'videojsVttThumbnails',
|
||||
input: 'src/plugin.js',
|
||||
output: [{
|
||||
file: 'dist/videojs-vtt-thumbnails.cjs.js',
|
||||
format: 'cjs'
|
||||
}, {
|
||||
file: 'dist/videojs-vtt-thumbnails.es.js',
|
||||
format: 'es'
|
||||
}],
|
||||
external: [
|
||||
'global',
|
||||
'global/document',
|
||||
'global/window',
|
||||
'video.js'
|
||||
],
|
||||
globals: {
|
||||
'video.js': 'videojs'
|
||||
},
|
||||
plugins: [
|
||||
json(),
|
||||
babel({
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
['es2015', {
|
||||
loose: true,
|
||||
modules: false
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
'external-helpers',
|
||||
'transform-object-assign'
|
||||
]
|
||||
})
|
||||
]
|
||||
};
|
59
scripts/test.rollup.config.js
Normal file
59
scripts/test.rollup.config.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Rollup configuration for packaging the plugin in a test bundle.
|
||||
*
|
||||
* This includes all dependencies for both the plugin and its tests.
|
||||
*/
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import json from 'rollup-plugin-json';
|
||||
import multiEntry from 'rollup-plugin-multi-entry';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
name: 'videojsVttThumbnailsTests',
|
||||
input: 'test/**/*.test.js',
|
||||
output: {
|
||||
file: 'test/dist/bundle.js',
|
||||
format: 'iife'
|
||||
},
|
||||
external: [
|
||||
'qunit',
|
||||
'qunitjs',
|
||||
'sinon',
|
||||
'video.js'
|
||||
],
|
||||
globals: {
|
||||
'qunit': 'QUnit',
|
||||
'qunitjs': 'QUnit',
|
||||
'sinon': 'sinon',
|
||||
'video.js': 'videojs'
|
||||
},
|
||||
plugins: [
|
||||
multiEntry({
|
||||
exports: false
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
main: true,
|
||||
jsnext: true
|
||||
}),
|
||||
json(),
|
||||
commonjs({
|
||||
sourceMap: false
|
||||
}),
|
||||
babel({
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
['es2015', {
|
||||
loose: true,
|
||||
modules: false
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
'external-helpers',
|
||||
'transform-object-assign'
|
||||
]
|
||||
})
|
||||
]
|
||||
};
|
50
scripts/umd.rollup.config.js
Normal file
50
scripts/umd.rollup.config.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Rollup configuration for packaging the plugin in a module that is consumable
|
||||
* as the `src` of a `script` tag or via AMD or similar client-side loading.
|
||||
*
|
||||
* This module DOES include its dependencies.
|
||||
*/
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import json from 'rollup-plugin-json';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
name: 'videojsVttThumbnails',
|
||||
input: 'src/plugin.js',
|
||||
output: {
|
||||
file: 'dist/videojs-vtt-thumbnails.js',
|
||||
format: 'umd'
|
||||
},
|
||||
external: [
|
||||
'video.js'
|
||||
],
|
||||
globals: {
|
||||
'video.js': 'videojs'
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true,
|
||||
main: true,
|
||||
jsnext: true
|
||||
}),
|
||||
json(),
|
||||
commonjs({
|
||||
sourceMap: false
|
||||
}),
|
||||
babel({
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
['es2015', {
|
||||
loose: true,
|
||||
modules: false
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
'external-helpers',
|
||||
'transform-object-assign'
|
||||
]
|
||||
})
|
||||
]
|
||||
};
|
10
scripts/version.js
Normal file
10
scripts/version.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
const execSync = require('child_process').execSync;
|
||||
const path = require('path');
|
||||
const semver = require('semver');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
if (!semver.prerelease(pkg.version)) {
|
||||
process.chdir(path.resolve(__dirname, '..'));
|
||||
execSync('conventional-changelog -p videojs -i CHANGELOG.md -s');
|
||||
execSync('git add CHANGELOG.md');
|
||||
}
|
346
src/plugin.js
Normal file
346
src/plugin.js
Normal file
|
@ -0,0 +1,346 @@
|
|||
import videojs from 'video.js'
|
||||
import { version as VERSION } from '../package.json'
|
||||
// import request from 'request';
|
||||
|
||||
// Default options for the plugin.
|
||||
const defaults = {}
|
||||
|
||||
// Cross-compatibility for Video.js 5 and 6.
|
||||
const registerPlugin = videojs.registerPlugin || videojs.plugin
|
||||
// const dom = videojs.dom || videojs;
|
||||
|
||||
/**
|
||||
* Function to invoke when the player is ready.
|
||||
*
|
||||
* This is a great place for your plugin to initialize itself. When this
|
||||
* function is called, the player will have its DOM and child components
|
||||
* in place.
|
||||
*
|
||||
* @function onPlayerReady
|
||||
* @param {Player} player
|
||||
* A Video.js player object.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* A plain object containing options for the plugin.
|
||||
*/
|
||||
const onPlayerReady = (player, options) => {
|
||||
player.addClass('vjs-vtt-thumbnails')
|
||||
new vttThumbnailsPlugin(player, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* A video.js plugin.
|
||||
*
|
||||
* In the plugin function, the value of `this` is a video.js `Player`
|
||||
* instance. You cannot rely on the player being in a "ready" state here,
|
||||
* depending on how the plugin is invoked. This may or may not be important
|
||||
* to you; if not, remove the wait for "ready"!
|
||||
*
|
||||
* @function vttThumbnails
|
||||
* @param {Object} [options={}]
|
||||
* An object of options left to the plugin author to define.
|
||||
*/
|
||||
const vttThumbnails = function (options) {
|
||||
this.ready(() => {
|
||||
onPlayerReady(this, videojs.mergeOptions(defaults, options))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* VTT Thumbnails class.
|
||||
*
|
||||
* This class performs all functions related to displaying the vtt
|
||||
* thumbnails.
|
||||
*/
|
||||
class vttThumbnailsPlugin {
|
||||
|
||||
/**
|
||||
* Plugin class constructor, called by videojs on
|
||||
* ready event.
|
||||
*
|
||||
* @function constructor
|
||||
* @param {Player} player
|
||||
* A Video.js player object.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* A plain object containing options for the plugin.
|
||||
*/
|
||||
constructor (player, options) {
|
||||
this.player = player
|
||||
this.options = options
|
||||
this.initializeThumbnails()
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap the plugin.
|
||||
*/
|
||||
initializeThumbnails () {
|
||||
if (!this.options.src) {
|
||||
return
|
||||
}
|
||||
const baseUrl = this.getBaseUrl()
|
||||
const url = this.getFullyQualifiedUrl(this.options.src, baseUrl)
|
||||
this.getVttFile(url)
|
||||
.then((data) => {
|
||||
this.vttData = this.processVtt(data)
|
||||
this.setupThumbnailElement()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a base URL should we require one.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getBaseUrl () {
|
||||
return [
|
||||
window.location.protocol,
|
||||
'//',
|
||||
window.location.hostname,
|
||||
(window.location.port ? ':' + window.location.port : ''),
|
||||
window.location.pathname
|
||||
].join('').split(/([^\/]*)$/gi).shift()
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the contents of the VTT file.
|
||||
*
|
||||
* @param url
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getVttFile (url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = new XMLHttpRequest()
|
||||
req.data = {
|
||||
resolve: resolve
|
||||
}
|
||||
req.addEventListener('load', this.vttFileLoaded)
|
||||
req.open('GET', url)
|
||||
req.send()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for loaded VTT file.
|
||||
*/
|
||||
vttFileLoaded () {
|
||||
this.data.resolve(this.responseText)
|
||||
}
|
||||
|
||||
setupThumbnailElement (data) {
|
||||
const mouseDisplay = this.player.$('.vjs-mouse-display')
|
||||
this.progressBar = this.player.$('.vjs-progress-control')
|
||||
const thumbHolder = document.createElement('div')
|
||||
thumbHolder.setAttribute('class', 'vjs-vtt-thumbnail-display')
|
||||
this.progressBar.appendChild(thumbHolder)
|
||||
this.thumbnailHolder = thumbHolder
|
||||
mouseDisplay.classList.add('vjs-hidden')
|
||||
this.progressBar.addEventListener('mouseenter', () => { return this.onBarMouseenter() })
|
||||
this.progressBar.addEventListener('mouseleave', () => { return this.onBarMouseleave() })
|
||||
}
|
||||
|
||||
onBarMouseenter () {
|
||||
this.mouseMoveCallback = (e) => { this.onBarMousemove(e) }
|
||||
this.progressBar.addEventListener('mousemove', this.mouseMoveCallback)
|
||||
this.showThumbnailHolder()
|
||||
}
|
||||
|
||||
onBarMouseleave () {
|
||||
this.progressBar.removeEventListener('mousemove', this.mouseMoveCallback)
|
||||
this.hideThumbnailHolder()
|
||||
}
|
||||
|
||||
onBarMousemove (event) {
|
||||
this.updateThumbnailStyle(
|
||||
event.clientX - this.progressBar.offsetLeft,
|
||||
this.progressBar.offsetWidth
|
||||
)
|
||||
}
|
||||
|
||||
getStyleForTime (time) {
|
||||
for (let i = 0; i < this.vttData.length; ++i) {
|
||||
let item = this.vttData[i]
|
||||
if (time >= item.start && time < item.end) {
|
||||
return item.css
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showThumbnailHolder () {
|
||||
this.thumbnailHolder.style.opacity = '1'
|
||||
}
|
||||
|
||||
hideThumbnailHolder () {
|
||||
this.thumbnailHolder.style.opacity = '0'
|
||||
}
|
||||
|
||||
updateThumbnailStyle (x, width) {
|
||||
const duration = this.player.duration()
|
||||
const time = ((1 - ((width - x) / width))) * duration
|
||||
const currentStyle = this.getStyleForTime(time)
|
||||
if (!currentStyle) {
|
||||
return this.hideThumbnailHolder()
|
||||
}
|
||||
const xPos = ((1 - ((width - x) / width))) * width
|
||||
this.thumbnailHolder.style.transform = 'translateX(' + xPos + 'px)'
|
||||
this.thumbnailHolder.style.marginLeft = '-' + (parseInt(currentStyle.width) / 2) + 'px'
|
||||
|
||||
if (this.lastStyle && this.lastStyle === currentStyle) {
|
||||
return
|
||||
}
|
||||
this.lastStyle = currentStyle
|
||||
|
||||
for (let style in currentStyle) {
|
||||
if (currentStyle.hasOwnProperty(style)) {
|
||||
this.thumbnailHolder.style[style] = currentStyle[style]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processVtt (data) {
|
||||
const processedVtts = []
|
||||
const vttDefinitions = data.split(/[\r\n][\r\n]/i)
|
||||
vttDefinitions.forEach((vttDef) => {
|
||||
if (vttDef.match(/([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?( ?--> ?)([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?[\r\n]{1}.*/gi)) {
|
||||
let vttDefSplit = vttDef.split(/[\r\n]/i)
|
||||
let vttTiming = vttDefSplit[0]
|
||||
let vttTimingSplit = vttTiming.split(/ ?--> ?/i)
|
||||
let vttTimeStart = vttTimingSplit[0]
|
||||
let vttTimeEnd = vttTimingSplit[1]
|
||||
let vttImageDef = vttDefSplit[1]
|
||||
let vttCssDef = this.getVttCss(vttImageDef)
|
||||
|
||||
processedVtts.push({
|
||||
start: this.getSecondsFromTimestamp(vttTimeStart),
|
||||
end: this.getSecondsFromTimestamp(vttTimeEnd),
|
||||
css: vttCssDef
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
return processedVtts
|
||||
}
|
||||
|
||||
getFullyQualifiedUrl (path, base) {
|
||||
if (!path.match(/\/\//i)) {
|
||||
return [
|
||||
this.trim(base, '/'),
|
||||
this.trim(path, '/')
|
||||
].join('/')
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
getPropsFromDef (def) {
|
||||
const imageDefSplit = def.split(/#xywh=/i)
|
||||
const imageUrl = imageDefSplit[0]
|
||||
const imageCoords = imageDefSplit[1]
|
||||
const splitCoords = imageCoords.match(/[0-9]+/gi)
|
||||
return {
|
||||
x: splitCoords[0],
|
||||
y: splitCoords[1],
|
||||
w: splitCoords[2],
|
||||
h: splitCoords[3],
|
||||
image: imageUrl
|
||||
}
|
||||
}
|
||||
|
||||
getVttCss (vttImageDef) {
|
||||
|
||||
const cssObj = {}
|
||||
|
||||
// If there isn't a protocol, use the VTT source URL.
|
||||
const baseSplit = this.options.src.split(/([^\/]*)$/gi)
|
||||
vttImageDef = this.getFullyQualifiedUrl(vttImageDef, baseSplit[0])
|
||||
|
||||
if (!vttImageDef.match(/#xywh=/i)) {
|
||||
cssObj.background = 'url("' + vttImageDef + '")'
|
||||
return cssObj
|
||||
}
|
||||
|
||||
const imageProps = this.getPropsFromDef(vttImageDef)
|
||||
cssObj.background = 'url("' + imageProps.image + '") no-repeat -' + imageProps.x + 'px -' + imageProps.y + 'px'
|
||||
cssObj.width = imageProps.w + 'px'
|
||||
cssObj.height = imageProps.h + 'px'
|
||||
|
||||
return cssObj
|
||||
}
|
||||
|
||||
doconstructTimestamp (timestamp) {
|
||||
const splitStampMilliseconds = timestamp.split('.')
|
||||
const timeParts = splitStampMilliseconds[0]
|
||||
const timePartsSplit = timeParts.split(':')
|
||||
return {
|
||||
milliseconds: parseInt(splitStampMilliseconds[1]) || 0,
|
||||
seconds: parseInt(timePartsSplit.pop()) || 0,
|
||||
minutes: parseInt(timePartsSplit.pop()) || 0,
|
||||
hours: parseInt(timePartsSplit.pop()) || 0,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getSecondsFromTimestamp (timestamp) {
|
||||
const timestampParts = this.doconstructTimestamp(timestamp)
|
||||
return parseInt((timestampParts.hours * (60 * 60)) +
|
||||
(timestampParts.minutes * 60) +
|
||||
timestampParts.seconds +
|
||||
(timestampParts.milliseconds * 1000))
|
||||
}
|
||||
|
||||
trim (str, charlist) {
|
||||
let whitespace = [
|
||||
' ',
|
||||
'\n',
|
||||
'\r',
|
||||
'\t',
|
||||
'\f',
|
||||
'\x0b',
|
||||
'\xa0',
|
||||
'\u2000',
|
||||
'\u2001',
|
||||
'\u2002',
|
||||
'\u2003',
|
||||
'\u2004',
|
||||
'\u2005',
|
||||
'\u2006',
|
||||
'\u2007',
|
||||
'\u2008',
|
||||
'\u2009',
|
||||
'\u200a',
|
||||
'\u200b',
|
||||
'\u2028',
|
||||
'\u2029',
|
||||
'\u3000'
|
||||
].join('')
|
||||
let l = 0
|
||||
let i = 0
|
||||
str += ''
|
||||
if (charlist) {
|
||||
whitespace = (charlist + '').replace(/([[\]().?/*{}+$^:])/g, '$1')
|
||||
}
|
||||
l = str.length
|
||||
for (i = 0; i < l; i++) {
|
||||
if (whitespace.indexOf(str.charAt(i)) === -1) {
|
||||
str = str.substring(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
l = str.length
|
||||
for (i = l - 1; i >= 0; i--) {
|
||||
if (whitespace.indexOf(str.charAt(i)) === -1) {
|
||||
str = str.substring(0, i + 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Register the plugin with video.js.
|
||||
registerPlugin('vttThumbnails', vttThumbnails)
|
||||
|
||||
// Include the version number.
|
||||
vttThumbnails.VERSION = VERSION
|
||||
|
||||
export default vttThumbnails
|
18
src/plugin.scss
Normal file
18
src/plugin.scss
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Sass for videojs-vtt-thumbnails
|
||||
|
||||
.video-js {
|
||||
|
||||
// This class is added to the video.js element by the plugin by default.
|
||||
&.vjs-vtt-thumbnails {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vjs-vtt-thumbnail-display {
|
||||
position: absolute;
|
||||
transition: transform .1s, opacity .2s;
|
||||
bottom: 85%;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 0 7px rgba(0,0,0,.6);
|
||||
}
|
||||
|
||||
}
|
19
test/index.html
Normal file
19
test/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>videojs-vtt-thumbnails Unit Tests</title>
|
||||
<link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
|
||||
<link rel="stylesheet" href="../node_modules/video.js/dist/video-js.css">
|
||||
<link href="../dist/videojs-vtt-thumbnails.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
|
||||
<script src="../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../node_modules/qunitjs/qunit/qunit.js"></script>
|
||||
<script src="../node_modules/video.js/dist/video.js"></script>
|
||||
<script src="../test/dist/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
43
test/karma.conf.js
Normal file
43
test/karma.conf.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
module.exports = function(config) {
|
||||
var detectBrowsers = {
|
||||
enabled: false,
|
||||
usePhantomJS: false
|
||||
};
|
||||
|
||||
// On Travis CI, we can only run in Firefox and Chrome; so, enforce that.
|
||||
if (process.env.TRAVIS) {
|
||||
config.browsers = ['Firefox', 'travisChrome'];
|
||||
}
|
||||
|
||||
// If no browsers are specified, we enable `karma-detect-browsers`
|
||||
// this will detect all browsers that are available for testing
|
||||
if (!config.browsers.length) {
|
||||
detectBrowsers.enabled = true;
|
||||
}
|
||||
|
||||
config.set({
|
||||
basePath: '..',
|
||||
frameworks: ['qunit', 'detectBrowsers'],
|
||||
files: [
|
||||
'node_modules/video.js/dist/video-js.css',
|
||||
'dist/videojs-vtt-thumbnails.css',
|
||||
|
||||
'node_modules/sinon/pkg/sinon.js',
|
||||
'node_modules/video.js/dist/video.js',
|
||||
'test/dist/bundle.js'
|
||||
],
|
||||
customLaunchers: {
|
||||
travisChrome: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
detectBrowsers: detectBrowsers,
|
||||
reporters: ['dots'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
autoWatch: false,
|
||||
singleRun: true,
|
||||
concurrency: Infinity
|
||||
});
|
||||
};
|
58
test/plugin.test.js
Normal file
58
test/plugin.test.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import document from 'global/document';
|
||||
|
||||
import QUnit from 'qunit';
|
||||
import sinon from 'sinon';
|
||||
import videojs from 'video.js';
|
||||
|
||||
import plugin from '../src/plugin';
|
||||
|
||||
const Player = videojs.getComponent('Player');
|
||||
|
||||
QUnit.test('the environment is sane', function(assert) {
|
||||
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
|
||||
assert.strictEqual(typeof sinon, 'object', 'sinon exists');
|
||||
assert.strictEqual(typeof videojs, 'function', 'videojs exists');
|
||||
assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
|
||||
});
|
||||
|
||||
QUnit.module('videojs-vtt-thumbnails', {
|
||||
|
||||
beforeEach() {
|
||||
|
||||
// Mock the environment's timers because certain things - particularly
|
||||
// player readiness - are asynchronous in video.js 5. This MUST come
|
||||
// before any player is created; otherwise, timers could get created
|
||||
// with the actual timer methods!
|
||||
this.clock = sinon.useFakeTimers();
|
||||
|
||||
this.fixture = document.getElementById('qunit-fixture');
|
||||
this.video = document.createElement('video');
|
||||
this.fixture.appendChild(this.video);
|
||||
this.player = videojs(this.video);
|
||||
},
|
||||
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('registers itself with video.js', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
assert.strictEqual(
|
||||
typeof Player.prototype.vttThumbnails,
|
||||
'function',
|
||||
'videojs-vtt-thumbnails plugin was registered'
|
||||
);
|
||||
|
||||
this.player.vttThumbnails();
|
||||
|
||||
// Tick the clock forward enough to trigger the player to be "ready".
|
||||
this.clock.tick(2);
|
||||
|
||||
assert.ok(
|
||||
this.player.hasClass('vjs-vtt-thumbnails'),
|
||||
'the plugin adds a class to the player'
|
||||
);
|
||||
});
|
Loading…
Reference in a new issue