diff --git a/docs/lab/css/artplayer-plugin-subtitle.css b/docs/lab/css/artplayer-plugin-subtitle.css
new file mode 100644
index 000000000..21f9dd122
--- /dev/null
+++ b/docs/lab/css/artplayer-plugin-subtitle.css
@@ -0,0 +1 @@
+.artplayerPluginDanmu{display:flex}
\ No newline at end of file
diff --git a/docs/lab/css/artplayer.css b/docs/lab/css/artplayer.css
index 17bb67296..f4288d2ef 100644
--- a/docs/lab/css/artplayer.css
+++ b/docs/lab/css/artplayer.css
@@ -1,4 +1,4 @@
-.artplayer-video-player{position:relative;width:100%;height:100%;overflow:hidden;outline:0;font-family:Roboto,Arial,Helvetica,sans-serif;color:#eee;text-align:left;direction:ltr;font-size:14px;line-height:1.3;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);touch-action:manipulation;-ms-high-contrast-adjust:none}.artplayer-video-player *{box-sizing:border-box}.artplayer-video-player .art-icon{display:inline-block;line-height:1.5}.artplayer-video-player .art-icon svg{fill:#fff}.artplayer-video-player .art-hide{display:none!important}
+.artplayer-video-player{position:relative;width:100%;height:100%;overflow:hidden;outline:0;font-family:Roboto,Arial,Helvetica,sans-serif;color:#eee;text-align:left;direction:ltr;font-size:14px;line-height:1.3;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);touch-action:manipulation;-ms-high-contrast-adjust:none}.artplayer-video-player *{box-sizing:border-box}.artplayer-video-player .art-icon{display:inline-block;line-height:1.5}.artplayer-video-player .art-icon svg{fill:#fff}
.artplayer-video-player .artplayer-video{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;width:100%;height:100%;background-color:#000;outline:0;cursor:pointer}
.artplayer-video-player .artplayer-subtitle{position:absolute;z-index:20;bottom:40px;width:100%;padding:0 20px;text-align:center;color:#fff;text-shadow:.5px .5px .5px rgba(0,0,0,.5);font-size:20px;pointer-events:none}.artplayer-video-player .artplayer-subtitle p{margin:5px 0}
.artplayer-video-player .artplayer-layers{position:absolute;z-index:30;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.artplayer-video-player .artplayer-layers .art-layer{pointer-events:all}
diff --git a/docs/lab/index.html b/docs/lab/index.html
index b994a2aae..183eff7bb 100644
--- a/docs/lab/index.html
+++ b/docs/lab/index.html
@@ -41,6 +41,7 @@
+
+```
+
+Will expose the global variable to `window.artplayerPluginSubtitle`.
+
+## Usage
+
+```html
+
+```
+
+```js
+var art = new Artplayer({
+ container: '.artplayer-app',
+ url: 'path/to/video.mp4',
+ plugins: [
+ artplayerPluginSubtitle({
+ time: -1, // Subtitle delay time, ranging from -10 to 10
+ }),
+ ],
+});
+```
+
+## License
+
+MIT © [Harvey Zack](https://www.zhw-island.com/)
diff --git a/packages/artplayer-plugin-subtitle/package.json b/packages/artplayer-plugin-subtitle/package.json
new file mode 100644
index 000000000..08ff02624
--- /dev/null
+++ b/packages/artplayer-plugin-subtitle/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "artplayer-plugin-subtitle",
+ "version": "1.0.3",
+ "description": "Danmu plugin for ArtPlayer",
+ "main": "dist/artplayer-plugin-subtitle.js",
+ "style": "dist/artplayer-plugin-subtitle.css",
+ "scripts": {
+ "dev": "cross-env NODE_ENV=development rollup -c -m -w",
+ "build": "cross-env NODE_ENV=production rollup -c",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/zhw2590582/ArtPlayer.git"
+ },
+ "keywords": [
+ "video",
+ "player"
+ ],
+ "author": "Harvey Zack ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/zhw2590582/ArtPlayer/issues"
+ },
+ "homepage": "https://github.com/zhw2590582/ArtPlayer#readme",
+ "devDependencies": {
+ "@babel/core": "^7.1.2",
+ "@babel/plugin-external-helpers": "^7.0.0",
+ "@babel/plugin-transform-runtime": "^7.1.0",
+ "@babel/preset-env": "^7.1.0",
+ "@babel/runtime": "^7.1.2",
+ "autoprefixer": "^9.3.1",
+ "browser-env": "^3.2.5",
+ "cross-env": "^5.2.0",
+ "cssnano": "^4.1.7",
+ "node-sass": "^4.9.4",
+ "rollup": "^0.67.0",
+ "rollup-plugin-babel": "^4.0.3",
+ "rollup-plugin-commonjs": "^9.2.0",
+ "rollup-plugin-eslint": "^5.0.0",
+ "rollup-plugin-node-resolve": "^3.4.0",
+ "rollup-plugin-postcss": "^1.6.2",
+ "rollup-plugin-replace": "^2.1.0",
+ "rollup-plugin-svgo": "^1.0.1",
+ "rollup-plugin-uglify": "^6.0.0"
+ },
+ "dependencies": {
+ "vtt-adjust": "^1.0.2"
+ }
+}
diff --git a/packages/artplayer-plugin-subtitle/rollup.config.js b/packages/artplayer-plugin-subtitle/rollup.config.js
new file mode 100644
index 000000000..5848aba0d
--- /dev/null
+++ b/packages/artplayer-plugin-subtitle/rollup.config.js
@@ -0,0 +1,63 @@
+const path = require('path');
+const babel = require('rollup-plugin-babel');
+const commonjs = require('rollup-plugin-commonjs');
+const nodeResolve = require('rollup-plugin-node-resolve');
+const postcss = require('rollup-plugin-postcss');
+const { eslint } = require('rollup-plugin-eslint');
+const autoprefixer = require('autoprefixer');
+const cssnano = require('cssnano');
+const replace = require('rollup-plugin-replace');
+const svgo = require('rollup-plugin-svgo');
+const { uglify } = require('rollup-plugin-uglify');
+const { version, homepage, name } = require('./package.json');
+
+const isProd = process.env.NODE_ENV === 'production';
+
+const banner =
+ '/*!\n' +
+ ` * ${name}.js v${version}\n` +
+ ` * Github: ${homepage}\n` +
+ ` * (c) 2017-${new Date().getFullYear()} Harvey Zack\n` +
+ ' * Released under the MIT License.\n' +
+ ' */\n';
+
+export default {
+ input: 'src/index.js',
+ output: {
+ name,
+ file: isProd ? `dist/${name}.js` : path.join(process.cwd(), '../../', `docs/lab/js/${name}.js`),
+ format: 'umd',
+ exports: 'named',
+ },
+ plugins: [
+ eslint({
+ exclude: ['node_modules/**', 'src/**/*.scss', 'src/**/*.svg'],
+ }),
+ postcss({
+ plugins: [autoprefixer, cssnano],
+ extract: isProd ? `dist/${name}.css` : path.join(process.cwd(), '../../', `docs/lab/css/${name}.css`),
+ }),
+ nodeResolve(),
+ commonjs(),
+ babel({
+ runtimeHelpers: true,
+ exclude: 'node_modules/**',
+ presets: [['@babel/env', { modules: false }]],
+ plugins: ['@babel/plugin-external-helpers', '@babel/plugin-transform-runtime'],
+ }),
+ svgo({
+ raw: true,
+ }),
+ replace({
+ exclude: 'node_modules/**',
+ __ENV__: JSON.stringify(process.env.NODE_ENV || 'development'),
+ __VERSION__: version,
+ }),
+ isProd &&
+ uglify({
+ output: {
+ preamble: banner,
+ },
+ }),
+ ],
+};
diff --git a/packages/artplayer-plugin-subtitle/src/index.js b/packages/artplayer-plugin-subtitle/src/index.js
new file mode 100644
index 000000000..31f0e47e5
--- /dev/null
+++ b/packages/artplayer-plugin-subtitle/src/index.js
@@ -0,0 +1,33 @@
+function artplayerPluginSubtitle(option) {
+ return art => {
+ const { errorHandle, clamp, sleep } = art.constructor.utils;
+
+ errorHandle(
+ option && typeof option.time === 'number',
+ `The plugin 'artplayerPluginSubtitle': 'option.time' require 'number' type, but got '${typeof option.time}'`,
+ );
+
+ let retry = 0;
+ const maxRetry = 10;
+ const time = clamp(option.time, -10, 10);
+
+ function adjust() {
+ errorHandle(retry < maxRetry, 'It seems that something wrong for reading subtitle');
+ retry += 1;
+ const cues = Array.from(art.refs.$track.track.cues);
+ if (cues.length === 0) {
+ sleep(100).then(adjust);
+ } else {
+ cues.forEach(cue => {
+ cue.startTime += time;
+ cue.endTime += time;
+ });
+ }
+ }
+
+ art.on('subtitle:load', adjust);
+ };
+}
+
+window.artplayerPluginSubtitle = artplayerPluginSubtitle;
+export default artplayerPluginSubtitle;
diff --git a/packages/artplayer/src/index.js b/packages/artplayer/src/index.js
index 4307728b9..0aef2f85c 100644
--- a/packages/artplayer/src/index.js
+++ b/packages/artplayer/src/index.js
@@ -71,6 +71,7 @@ class Artplayer extends Emitter {
quality: [],
controls: [],
highlight: [],
+ plugins: [],
thumbnails: {
url: '',
number: 60,
@@ -90,18 +91,6 @@ class Artplayer extends Emitter {
};
}
- static use(...args) {
- const plugin = args[0];
- const installedPlugins = this.plugins || (this.plugins = []);
- if (installedPlugins.indexOf === -1) {
- installedPlugins.push(plugin);
- args.unshift(this);
- plugin(...args.slice(1));
- this.prototype.emit('use', plugin);
- }
- return this;
- }
-
init() {
this.isFocus = false;
this.isPlaying = false;
@@ -136,6 +125,20 @@ class Artplayer extends Emitter {
id += 1;
this.id = id;
Artplayer.instances.push(this);
+
+ this.plugins = [];
+ this.option.plugins.forEach((plugin, index) => {
+ const resule = plugin(this);
+ let pluginName = '';
+ if (resule && resule.name) {
+ pluginName = resule.name;
+ } else if (plugin.name) {
+ pluginName = plugin.name;
+ } else {
+ pluginName = `plugin${index}`;
+ }
+ this.plugins[pluginName] = resule;
+ });
}
destroy(removeHtml = false) {
diff --git a/packages/artplayer/src/scheme/index.js b/packages/artplayer/src/scheme/index.js
index 1c5c0a4c4..9c52c9742 100644
--- a/packages/artplayer/src/scheme/index.js
+++ b/packages/artplayer/src/scheme/index.js
@@ -32,6 +32,12 @@ export default {
mutex: 'boolean',
fullscreen: 'boolean',
fullscreenWeb: 'boolean',
+ plugins: {
+ type: 'array',
+ child: {
+ type: 'function',
+ },
+ },
layers: {
type: 'array',
child: {
diff --git a/packages/artplayer/src/style/artplayer.scss b/packages/artplayer/src/style/artplayer.scss
index 0bbba3bba..53f8bfe32 100644
--- a/packages/artplayer/src/style/artplayer.scss
+++ b/packages/artplayer/src/style/artplayer.scss
@@ -26,8 +26,4 @@
fill: #fff;
}
}
-
- .art-hide {
- display: none !important;
- }
}
\ No newline at end of file
diff --git a/packages/artplayer/src/subtitle.js b/packages/artplayer/src/subtitle.js
index b96dd1f10..9bb66c547 100644
--- a/packages/artplayer/src/subtitle.js
+++ b/packages/artplayer/src/subtitle.js
@@ -5,7 +5,6 @@ export default class Subtitle {
constructor(art) {
this.art = art;
this.state = true;
- this.vttText = '';
const { url } = this.art.option.subtitle;
if (url) {
this.init();
@@ -22,10 +21,11 @@ export default class Subtitle {
const $track = document.createElement('track');
$track.default = true;
$track.kind = 'metadata';
- this.load(subtitle.url).then(data => {
- $track.src = data;
- $video.appendChild($track);
- this.art.refs.$track = $track;
+ $video.appendChild($track);
+ this.art.refs.$track = $track;
+ this.load(subtitle.url).then(vttText => {
+ $track.src = vttToBlob(vttText);
+ this.art.emit('subtitle:load', vttText);
if ($video.textTracks && $video.textTracks[0]) {
const [track] = $video.textTracks;
proxy(track, 'cuechange', () => {
@@ -54,12 +54,13 @@ export default class Subtitle {
return response.text();
})
.then(text => {
+ let vttText = '';
if (/x-subrip/gi.test(type)) {
- this.vttText = srtToVtt(text);
+ vttText = srtToVtt(text);
} else {
- this.vttText = text;
+ vttText = text;
}
- return vttToBlob(this.vttText);
+ return vttText;
})
.catch(err => {
notice.show(err);
@@ -103,8 +104,9 @@ export default class Subtitle {
switch(url) {
const { $track } = this.art.refs;
errorHandle($track, 'You need to initialize the subtitle option first.');
- this.load(url).then(data => {
- $track.src = data;
+ this.load(url).then(vttText => {
+ $track.src = vttToBlob(vttText);
+ this.art.emit('subtitle:load', vttText);
this.art.emit('subtitle:switch', url);
});
}