├── __test__
├── index.js
├── play.js
└── e2e
│ └── index.js
├── src
├── .DS_Store
├── image
│ ├── .DS_Store
│ ├── splay.svg
│ ├── spause.svg
│ ├── vmute.svg
│ ├── vlow.svg
│ ├── play.svg
│ ├── pause.svg
│ ├── enlarge.svg
│ ├── shrink.svg
│ ├── vhigh.svg
│ ├── danma-on.svg
│ ├── danma-off.svg
│ └── loading.svg
├── component.js
├── progresstime.js
├── README.md
├── base.js
├── playbackrate.js
├── createchild.js
├── play.js
├── clarity.js
├── screen.js
├── progressbar.js
├── volume.js
├── index.js
└── control.css
├── coverage
└── chrome
│ ├── sort-arrow-sprite.png
│ ├── prettify.css
│ ├── index.html
│ ├── sorter.js
│ ├── base.css
│ ├── src
│ └── index.html
│ └── prettify.js
├── .stylelintrc
├── dev.md
├── demo
├── icon
│ ├── play.svg
│ ├── pause.svg
│ ├── volume-low.svg
│ ├── screen-full.svg
│ ├── screen-small.svg
│ ├── volume-high.svg
│ └── volume-mute.svg
└── index.html
├── .gitignore
├── package.json
├── karma.conf.js
├── .eslintrc
└── README.md
/__test__/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimeejs/chimee-plugin-controlbar/HEAD/src/.DS_Store
--------------------------------------------------------------------------------
/__test__/play.js:
--------------------------------------------------------------------------------
1 | import Play from '../src/play';
2 |
3 | describe('init', function () {
4 |
5 | });
6 |
--------------------------------------------------------------------------------
/src/image/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimeejs/chimee-plugin-controlbar/HEAD/src/image/.DS_Store
--------------------------------------------------------------------------------
/coverage/chrome/sort-arrow-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimeejs/chimee-plugin-controlbar/HEAD/coverage/chrome/sort-arrow-sprite.png
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "rules": {
4 | "selector-type-no-unknown": [true, {
5 | "ignore": ["custom-elements", "default-namespace"]
6 | }]
7 | }
8 | }
--------------------------------------------------------------------------------
/dev.md:
--------------------------------------------------------------------------------
1 | # 开发笔记
2 |
3 | ## 整理思路
4 |
5 | 1. js 来做通过控制类来调整布局的变化。 css 主要由用户去写, 而不是动态生成
6 |
7 | ## 记下来要做的事情
8 |
9 | 1. 文档补齐,测试用例补齐,代码整理
10 | * 整理 package.json, build 目录
11 | * eslint
12 | 1. 尝试使用 flex 布局(table 布局)
13 | 1. 思考用户 css 自定义 html event
14 |
15 | ## 总结的思路
16 |
17 | 1. 类似于 smll play 的类名, 会被覆盖掉,所以类名得复杂一点了
18 | 2. 组件的话, 用户可以替换 icon 但是不可以替换 html
19 |
20 | ## 测试点
21 |
22 | 1. 用户配置 -> init html event
23 | 2. 设定的几个事件的执行情况
--------------------------------------------------------------------------------
/src/component.js:
--------------------------------------------------------------------------------
1 | import {addClassName} from 'chimee-helper';
2 | import Base from './base.js';
3 |
4 | /**
5 | * 自定义组件配置
6 | */
7 |
8 | export default class Component extends Base {
9 | constructor (parent, option) {
10 | super(parent);
11 | this.option = option;
12 | this.init();
13 | }
14 |
15 | init () {
16 | super.create();
17 | addClassName(this.$dom, 'chimee-flex-component');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/image/splay.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/spause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/coverage/chrome/prettify.css:
--------------------------------------------------------------------------------
1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
2 |
--------------------------------------------------------------------------------
/demo/icon/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/image/vmute.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/vlow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/image/pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/icon/pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/image/enlarge.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/shrink.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/vhigh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/progresstime.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, formatTime, $} from 'chimee-helper';
2 | import Base from './base.js';
3 |
4 | /**
5 | * progressTime 配置
6 | */
7 |
8 | const defaultOption = {
9 | tag: 'chimee-progresstime',
10 | html: `
11 | 00:00/00:00
15 |
16 | `
17 | };
18 |
19 | export default class ProgressTime extends Base {
20 | constructor (parent, option) {
21 | super(parent);
22 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
23 | this.init();
24 | }
25 |
26 | init () {
27 | super.create();
28 | this.$dom = $(this.$dom);
29 | this.$total = this.$dom.find('chimee-progresstime-total-value');
30 | this.$pass = this.$dom.find('chimee-progresstime-pass');
31 | this.$dom.addClass('chimee-flex-component');
32 | }
33 |
34 | updatePass () {
35 | this.$pass.text(formatTime(this.parent.currentTime));
36 | }
37 |
38 | updateTotal () {
39 | this.$total.text(formatTime(this.parent.duration));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | # control
2 |
3 | ## 介绍
4 |
5 | 1. 定义了组件基础类
6 |
7 | 1. 定义了官方组件
8 |
9 | 1. 定义了扩展自定义组件的方式
10 |
11 | ## 第一阶段优化
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 1. 尽量不要使用默认 css 值来做什么事情, 尽量计算, 防止用户复写 css 后产生的 js 还是拿自己默认的 css 去写, 因为不同的机器/浏览器, 获取到 css 值不同
40 |
41 | 1. 有些数据流还是双向的。 可以尽量做到单向,
42 |
43 | ## 注意
44 |
45 | 1. 采用 table 布局, 根据内容撑开 cell, 可以设置 cell 中, 子元素的宽度来决定父元素的宽度
46 |
47 | 1. 鉴于 html 假如用户自定义的话, 则之前写好的组件也就没什么意义了。还不如用户重新写一个方便, 所以把 html 从已有组件用户配置项中移除 (提示用户, html 属性只有在用户完全自定义时有效, 图标组件, 提供 icon 属性, 可以替换默认 html, 较为复杂逻辑建议自定义组件)
48 |
49 | 1. css 长度,有的需要用户写一份 css 样式表来覆盖, 有的是 js 运算出来的。
50 |
51 | ## 待完善组件列表
52 |
53 | 1. 刷新
54 |
55 | 1. 清晰度
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### OSX ###
2 | *.DS_Store
3 | .AppleDouble
4 | .LSOverride
5 |
6 | # Icon must end with two
7 | Icon
8 | # Thumbnails
9 | ._*
10 | # Files that might appear in the root of a volume
11 | .DocumentRevisions-V100
12 | .fseventsd
13 | .Spotlight-V100
14 | .TemporaryItems
15 | .Trashes
16 | .VolumeIcon.icns
17 | .com.apple.timemachine.donotpresent
18 | # Directories potentially created on remote AFP share
19 | .AppleDB
20 | .AppleDesktop
21 | Network Trash Folder
22 | Temporary Items
23 | .apdisk
24 |
25 | ### Node ###
26 | # Logs
27 | logs
28 | *.log
29 | npm-debug.log*
30 |
31 | # Runtime data
32 | pids
33 | *.pid
34 | *.seed
35 | *.pid.lock
36 |
37 | # Directory for instrumented libs generated by jscoverage/JSCover
38 | lib-cov
39 |
40 | # Coverage directory used by tools like istanbul
41 | coverage
42 |
43 | # nyc test coverage
44 | .nyc_output
45 |
46 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
47 | .grunt
48 |
49 | # node-waf configuration
50 | .lock-wscript
51 |
52 | # Compiled binary addons (http://nodejs.org/api/addons.html)
53 | build/Release
54 |
55 | # Dependency directories
56 | node_modules
57 | jspm_packages
58 |
59 | # Optional npm cache directory
60 | .npm
61 |
62 | # Optional eslint cache
63 | .eslintcache
64 |
65 | # Optional REPL history
66 | .node_repl_history
67 |
68 | # Output of 'npm pack'
69 | *.tgz
70 |
71 | # Yarn Integrity file
72 | .yarn-integrity
73 |
74 | # Nuxt build
75 | .nuxt
76 |
77 | # Nuxt generate
78 | dist
--------------------------------------------------------------------------------
/src/image/danma-on.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/icon/volume-low.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/base.js:
--------------------------------------------------------------------------------
1 | import {addEvent, removeEvent} from 'chimee-helper';
2 | import {bind} from 'toxic-utils';
3 |
4 | export default class Base {
5 | constructor (parent) {
6 | this.parent = parent;
7 | }
8 |
9 | create () {
10 | this.createEl();
11 | this.addAllEvent();
12 | this.option.create && this.option.create.apply(this);
13 | }
14 |
15 | destroy () {
16 | this.removeAllEvent();
17 | this.option.destroy && this.option.destroy.apply(this);
18 | }
19 |
20 | createEl () {
21 | this.$dom = document.createElement(this.option.tag);
22 | typeof this.option.html === 'string'
23 | ? this.$dom.innerHTML = this.option.html
24 | : this.$dom.appendChild(this.option.html);
25 | this.parent.$wrap.appendChild(this.$dom);
26 | }
27 |
28 | addAllEvent () {
29 | this.option.defaultEvent && Object.keys(this.option.defaultEvent).forEach(item => {
30 | const key = this.option.defaultEvent[item];
31 | this[key] = bind(this[key], this);
32 | addEvent(this.$dom, item, this[key], false, false);
33 | });
34 | this.option.event && Object.keys(this.option.event).forEach(item => {
35 | const key = '__' + item;
36 | this[key] = bind(this.option.event[item], this);
37 | addEvent(this.$dom, item, this[key], false, false);
38 | });
39 | }
40 |
41 | removeAllEvent () {
42 | if(!(this.$dom instanceof HTMLElement)) {
43 | this.$dom = this.$dom[0];
44 | }
45 | this.option.defaultEvent && Object.keys(this.option.defaultEvent).forEach(item => {
46 | const key = this.option.defaultEvent[item];
47 | this[key] = bind(this[key], this);
48 | removeEvent(this.$dom, item, this[key], false, false);
49 | });
50 | this.option.event && Object.keys(this.option.event).forEach(item => {
51 | const key = '__' + item;
52 | this[key] = bind(this.option.event[item], this);
53 | removeEvent(this.$dom, item, this[key], false, false);
54 | });
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/demo/icon/screen-full.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/icon/screen-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/icon/volume-high.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chimee-plugin-controlbar",
3 | "version": "0.4.11",
4 | "description": "controlbar for chimee",
5 | "main": "lib/index.js",
6 | "module": "lib/index.mjs",
7 | "jsnext:main": "lib/index.mjs",
8 | "browser": "lib/index.browser.js",
9 | "scripts": {
10 | "test": "karma start karma.conf.js",
11 | "lint": "eslint ./src --fix",
12 | "build": "npm run b-common && npm run b-es && npm run b-umd && npm run b-min",
13 | "b-common": "rollup -c build/rollup.config.common.js",
14 | "b-es": "rollup -c build/rollup.config.es.js",
15 | "b-umd": "rollup -c build/rollup.config.umd.js",
16 | "b-min": "rollup -c build/rollup.config.min.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/Chimeejs/chimee-plugin-controlbar.git"
21 | },
22 | "keywords": [
23 | "plugin",
24 | "chimee",
25 | "controlbar",
26 | "video"
27 | ],
28 | "author": "yandeqiang",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/Chimeejs/chimee-plugin-controlbar/issues"
32 | },
33 | "homepage": "https://github.com/Chimeejs/chimee-plugin-controlbar#readme",
34 | "dependencies": {
35 | "babel-eslint": "^9.0.0",
36 | "chimee-helper": "^0.2.11"
37 | },
38 | "devDependencies": {
39 | "babel-core": "^6.26.0",
40 | "babel-plugin-external-helpers": "^6.22.0",
41 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
42 | "babel-plugin-transform-runtime": "^6.23.0",
43 | "babel-preset-env": "^1.6.1",
44 | "babel-preset-es2015-rollup": "^3.0.0",
45 | "babel-preset-latest": "^6.24.1",
46 | "babel-preset-stage-0": "^6.24.1",
47 | "chimee": "^0.9.5",
48 | "cssnano": "^4.1.10",
49 | "postcss": "^6.0.14",
50 | "postcss-cssnext": "^3.0.2",
51 | "postcss-reporter": "^5.0.0",
52 | "rollup": "^0.50.0",
53 | "rollup-plugin-babel": "^3.0.2",
54 | "rollup-plugin-commonjs": "^8.2.6",
55 | "rollup-plugin-node-resolve": "^3.0.0",
56 | "rollup-plugin-postcss": "^0.5.5",
57 | "rollup-plugin-replace": "^2.0.0",
58 | "rollup-plugin-uglify": "^2.0.1",
59 | "stylelint": "^11.1.1",
60 | "stylelint-config-standard": "^17.0.0",
61 | "toxic-decorators": "^0.3.8"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/demo/icon/volume-mute.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/image/danma-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/playbackrate.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, addClassName, removeClassName, setStyle, $} from 'chimee-helper';
2 | import Base from './base.js';
3 |
4 | /**
5 | * playbackrate 配置
6 | */
7 |
8 | const defaultOption = {
9 | tag: 'chimee-playbackrate',
10 | width: '4em',
11 | html: `
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 | `,
24 | defaultEvent: {
25 | click: 'click'
26 | }
27 | };
28 |
29 | export default class Playbackrate extends Base {
30 | constructor (parent, option) {
31 | super(parent);
32 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
33 | this.init();
34 | }
35 |
36 | init () {
37 | super.create();
38 | addClassName(this.$dom, 'chimee-flex-component');
39 |
40 | this.$text = $(this.$dom).find('chimee-playbackrate-text');
41 | this.$list = $(this.$dom).find('chimee-playbackrate-list');
42 | this.$listUl = this.$list.find('ul');
43 |
44 | // 用户自定义配置
45 | this.option.width && setStyle(this.$dom, 'width', this.option.width);
46 |
47 | this.initTextList();
48 | }
49 |
50 | initTextList () {
51 | const length = this.option.list.length;
52 | let hasRenderDefault = false;
53 | this.option.list.forEach((item, i) => {
54 | const li = $(document.createElement('li'));
55 | li.attr('data-value', item.value);
56 | li.text(item.name);
57 | if(item.default || (!hasRenderDefault && i === length - 1)) {
58 | hasRenderDefault = true;
59 | !item.default && console.warn('播放速率列表需要给每项配置 `default(boolean)` 来标明是否是默认播放速率');
60 | this.$text.text(item.name);
61 | li.addClass('active');
62 | this.switchPlaybackrate(item.value);
63 | }
64 | this.$listUl.append(li);
65 | });
66 | }
67 |
68 | click (e) {
69 | const elem = e.target;
70 | if(elem.tagName === 'LI') {
71 | const rate = elem.getAttribute('data-value') || 1;
72 | this.switchPlaybackrate(rate);
73 | Array.from(elem.parentElement.children).map(item => {
74 | removeClassName(item, 'active');
75 | });
76 | addClassName(e.target, 'active');
77 | this.$text.text(e.target.textContent);
78 |
79 | }
80 | }
81 |
82 | switchPlaybackrate (rate) {
83 | setTimeout(_ => {
84 | this.parent.playbackRate = rate;
85 | }, 0);
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/createchild.js:
--------------------------------------------------------------------------------
1 | import Component from './component.js';
2 | import Play from './play.js';
3 | import Volume from './volume.js';
4 | import ProgressBar from './progressbar.js';
5 | import ProgressTime from './progresstime.js';
6 | import Screen from './screen.js';
7 | import Clarity from './clarity.js';
8 | import Playbackrate from './playbackrate.js';
9 |
10 | function hundleChildren (plugin) {
11 | let childConfig = {};
12 | if(!plugin.$config.children) {
13 | childConfig = plugin.isLive ? {
14 | play: true, // 底部播放暂停按钮
15 | progressTime: false, // 播放时间
16 | progressBar: false, // 播放进度控制条
17 | volume: true, // 声音控制
18 | screen: true, // 全屏控制
19 | } : {
20 | play: true, // 底部播放暂停按钮
21 | progressTime: true, // 播放时间
22 | progressBar: true, // 播放进度控制条
23 | volume: true, // 声音控制
24 | screen: true, // 全屏控制
25 | };
26 | }else{
27 | childConfig = plugin.$config.children;
28 | }
29 |
30 | return childConfig;
31 | }
32 |
33 | /**
34 | * 1. 将所有的 ui component 输出到 html 结构中
35 | * 2. 为这些 component 绑定响应的事件
36 | * @param {*} dom 所有 ui 节点的子容器
37 | * @param {*} config 关于 ui 的一些列设置
38 | * @return {Array} 所有子节点
39 | */
40 |
41 | export function createChild (plugin) {
42 | const childConfig = plugin.config.children = hundleChildren(plugin);
43 | const children = {};
44 | if(!childConfig) {
45 | children.play = new Play(plugin);
46 | children.progressTime = new ProgressTime(plugin);
47 | children.progressBar = new ProgressBar(plugin);
48 | children.volume = new Volume(plugin);
49 | children.screen = new Screen(plugin);
50 | }else{
51 | Object.keys(childConfig).forEach(item => {
52 | switch(item) {
53 | case 'play':
54 | if(childConfig.play) {
55 | children.play = new Play(plugin, childConfig.play);
56 | }
57 | break;
58 | case 'progressTime':
59 | if(childConfig.progressTime) {
60 | children.progressTime = new ProgressTime(plugin, childConfig.progressTime);
61 | }
62 | break;
63 | case 'progressBar':
64 | children.progressBar = new ProgressBar(plugin, childConfig.progressBar);
65 | break;
66 | case 'volume':
67 | if(childConfig.volume) {
68 | children.volume = new Volume(plugin, childConfig.volume);
69 | }
70 | break;
71 | case 'screen':
72 | if(childConfig.screen) {
73 | children.screen = new Screen(plugin, childConfig.screen);
74 | }
75 | break;
76 | case 'clarity':
77 | if(childConfig.clarity) {
78 | children.clarity = new Clarity(plugin, childConfig.clarity);
79 | }
80 | break;
81 | case 'playbackrate':
82 | if(childConfig.playbackrate && Array.isArray(childConfig.playbackrate.list) && childConfig.playbackrate.list.length) {
83 | children.playbackrate = new Playbackrate(plugin, childConfig.playbackrate);
84 | }
85 | break;
86 | default:
87 | children[item] = new Component(plugin, childConfig[item]);
88 | break;
89 | }
90 | });
91 | }
92 |
93 | return children;
94 | }
95 |
--------------------------------------------------------------------------------
/src/play.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, addClassName, removeClassName, $} from 'chimee-helper';
2 | import Base from './base.js';
3 |
4 | /**
5 | * play 配置
6 | */
7 |
8 | const defaultOption = {
9 | tag: 'chimee-control-state',
10 | html: `
11 |
12 |
13 | `,
14 | animate: {
15 | icon: `
16 |
22 | `,
23 | path: {
24 | play: {
25 | left: 'M0.921875,0.265625L0.921875,197.074852L95.7890625,149L96.2929688,49Z',
26 | right: 'M90.3142151,45.9315226L90.3142151,151.774115L201.600944,99.9938782L201.600944,98.0237571Z'
27 | },
28 | pause: {
29 | left: 'M0.921875,1.265625L0.921875,198.074852L79.3611677,198.074852L79.3611677,0.258923126Z',
30 | right: 'M126.921875,1.265625L126.921875,198.074852L205.361168,198.074852L205.361168,0.258923126Z'
31 | }
32 | }
33 | },
34 | defaultEvent: {
35 | click: 'click'
36 | }
37 | };
38 |
39 | export default class Play extends Base {
40 | constructor (parent, option) {
41 | super(parent);
42 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
43 | this.animate = false;
44 | this.init();
45 | }
46 |
47 | init () {
48 | // 创建 html / 绑定事件
49 | super.create();
50 | this.$dom = $(this.$dom);
51 | this.$dom.addClass('chimee-flex-component');
52 |
53 | // 判断是否是默认或者用户提供 icon
54 | if(this.option.icon && this.option.icon.play && this.option.icon.pause) {
55 | this.$play = this.$dom.find('chimee-control-state-play');
56 | this.$pause = this.$dom.find('chimee-control-state-pause');
57 | this.$play.html(this.option.icon.play);
58 | this.$pause.html(this.option.icon.pause);
59 | }else if(!this.option.bitmap) {
60 | this.animate = true;
61 | this.option.animate.path = this.option.path ? this.option.path : this.option.animate.path;
62 | this.$dom.html(this.option.animate.icon);
63 | this.$left = this.$dom.find('.left');
64 | this.$right = this.$dom.find('.right');
65 | }
66 | this.changeState('pause');
67 | }
68 |
69 | changeState (state) {
70 | const nextState = state === 'play' ? 'pause' : 'play';
71 | this.state = state;
72 | addClassName(this.parent.$dom, nextState);
73 | removeClassName(this.parent.$dom, state);
74 | this.animate && this.setPath(nextState);
75 | }
76 |
77 | setPath (state) {
78 | const path = this.option.animate.path;
79 | if(state === 'play') {
80 | this.$left.attr('d', path.play.left);
81 | this.$right.attr('d', path.play.right);
82 | }else{
83 | this.$left.attr('d', path.pause.left);
84 | this.$right.attr('d', path.pause.right);
85 | }
86 | }
87 |
88 | click (e) {
89 | const nextState = this.state === 'play' ? 'pause' : 'play';
90 | this.changeState(nextState);
91 | this.parent.$emit(nextState);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/clarity.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, addClassName, removeClassName, setStyle, $} from 'chimee-helper';
2 | import Base from './base.js';
3 |
4 | /**
5 | * clarity 配置
6 | */
7 |
8 | const defaultOption = {
9 | tag: 'chimee-clarity',
10 | width: '2em',
11 | html: `
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 | `,
24 | defaultEvent: {
25 | click: 'click'
26 | },
27 | duration: 3,
28 | bias: 0,
29 | increment: 0,
30 | repeatTimes: 0,
31 | immediate: false,
32 | abort: false,
33 | list: []
34 | };
35 |
36 | export default class Clarity extends Base {
37 | constructor (parent, option) {
38 | super(parent);
39 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
40 | this.init();
41 | }
42 |
43 | init () {
44 | super.create();
45 | addClassName(this.$dom, 'chimee-flex-component');
46 |
47 | this.$text = $(this.$dom).find('chimee-clarity-text');
48 | this.$list = $(this.$dom).find('chimee-clarity-list');
49 | this.$listUl = this.$list.find('ul');
50 |
51 | // 用户自定义配置
52 | this.option.width && setStyle(this.$dom, 'width', this.option.width);
53 |
54 | const list = this.option.list;
55 | if(!list.length) {
56 | return setStyle(this.$dom, {
57 | display: 'none'
58 | });
59 | }
60 | this.initTextList(this.option.list);
61 | }
62 |
63 | initTextList (list) {
64 | this.$listUl.html('');
65 | list.forEach(item => {
66 | const li = $(document.createElement('li'));
67 | li.attr('data-url', item.src);
68 | li.text(item.name);
69 | if(item.src === this.parent.$videoConfig.src) {
70 | this.$text.text(item.name);
71 | li.addClass('active');
72 | }
73 | this.$listUl.append(li);
74 | });
75 | }
76 |
77 | click (e) {
78 | const elem = e.target;
79 | if(elem.tagName === 'LI') {
80 | const url = elem.getAttribute('data-url') || '';
81 | this.switchClarity(url).then(() => {
82 | this.loadOption = undefined;
83 | Array.from(elem.parentElement.children).map(item => {
84 | removeClassName(item, 'active');
85 | });
86 | addClassName(e.target, 'active');
87 | this.$text.text(e.target.textContent);
88 | }).catch((e) => {
89 | console.warn(e);
90 | });
91 | }
92 | }
93 |
94 | switchClarity (url) {
95 | if (this.loadOption) {
96 | this.loadOption.abort = true;
97 | }
98 | const {duration, bias, increment, repeatTimes, immediate, abort} = this.option;
99 | this.loadOption = {
100 | duration,
101 | bias,
102 | increment,
103 | repeatTimes,
104 | immediate,
105 | abort
106 | };
107 | return this.parent.$silentLoad(url, this.loadOption);
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Thu Aug 03 2017 16:41:10 GMT+0800 (CST)
3 |
4 | const babel = require('rollup-plugin-babel');
5 | const resolve = require('rollup-plugin-node-resolve');
6 | const commonjs = require('rollup-plugin-commonjs');
7 | const postcss = require('rollup-plugin-postcss');
8 | const replace = require('rollup-plugin-replace');
9 | // PostCSS plugins
10 | const cssnext = require('postcss-cssnext');
11 |
12 | module.exports = function (config) {
13 | config.set({
14 |
15 | // base path that will be used to resolve all patterns (eg. files, exclude)
16 | basePath: '',
17 |
18 | // frameworks to use
19 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
20 | frameworks: ['jasmine'],
21 |
22 | // list of files / patterns to load in the browser
23 | files: [
24 | {pattern: 'src/**.js', watched: false},
25 | {pattern: '__test__/**/**.js', watched: true},
26 | ],
27 |
28 | // list of files to exclude
29 | exclude: [
30 | 'node_modules/*'
31 | ],
32 |
33 | // preprocess matching files before serving them to the browser
34 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
35 | preprocessors: {
36 | 'src/**.js': ['rollup', 'coverage'],
37 | '__test__/**/**.js': ['rollup']
38 | },
39 | rollupPreprocessor: {
40 | plugins: [
41 | postcss({
42 | plugins: [
43 | cssnext()
44 | ],
45 | extensions: ['.css']
46 | }),
47 | babel({
48 | presets: ['es2015-rollup', 'stage-0'],
49 | plugins: [
50 | 'transform-decorators-legacy',
51 | 'transform-runtime'
52 | ],
53 | exclude: 'node_modules/**',
54 | runtimeHelpers: true,
55 | babelrc: false
56 | }),
57 | resolve(),
58 | commonjs(),
59 | replace({
60 | 'process.env.NODE_ENV': '"production"'
61 | })
62 | ],
63 | format: 'iife',
64 | name: 'controlbar',
65 | sourcemap: 'inline'
66 | },
67 | // test results reporter to use
68 | // possible values: 'dots', 'progress'
69 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
70 | reporters: ['progress', 'coverage'],
71 |
72 | coverageReporter: {
73 | type: 'html',
74 | dir: 'coverage/',
75 | subdir (browser) {
76 | return browser.toLowerCase().split(/[ /-]/)[0];
77 | }
78 | },
79 |
80 | // web server port
81 | port: 9876,
82 |
83 | // enable / disable colors in the output (reporters and logs)
84 | colors: true,
85 |
86 | // level of logging
87 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
88 | logLevel: config.LOG_INFO,
89 |
90 | // enable / disable watching file and executing tests whenever any file changes
91 | autoWatch: true,
92 |
93 | // start these browsers
94 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
95 | browsers: ['Chrome'],
96 |
97 | // Continuous Integration mode
98 | // if true, Karma captures browsers, runs the tests and exits
99 | singleRun: false,
100 |
101 | // Concurrency level
102 | // how many browser should be started simultaneous
103 | concurrency: Infinity,
104 |
105 | client: {
106 | captureConsole: true
107 | }
108 | });
109 | };
110 |
--------------------------------------------------------------------------------
/src/image/loading.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/coverage/chrome/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for All files
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 | /
20 |
21 |
22 |
23 | 34.77%
24 | Statements
25 | 7788/22400
26 |
27 |
28 | 17.65%
29 | Branches
30 | 2659/15064
31 |
32 |
33 | 24.41%
34 | Functions
35 | 1115/4568
36 |
37 |
38 | 36.51%
39 | Lines
40 | 7531/20628
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | | File |
50 | |
51 | Statements |
52 | |
53 | Branches |
54 | |
55 | Functions |
56 | |
57 | Lines |
58 | |
59 |
60 |
61 |
62 | | src/ |
63 | |
64 | 34.77% |
65 | 7788/22400 |
66 | 17.65% |
67 | 2659/15064 |
68 | 24.41% |
69 | 1115/4568 |
70 | 36.51% |
71 | 7531/20628 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/screen.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, addClassName, removeClassName, $} from 'chimee-helper';
2 | import {autobind} from 'toxic-decorators';
3 | import Base from './base.js';
4 |
5 | /**
6 | * Screen 配置
7 | */
8 |
9 | const defaultOption = {
10 | tag: 'chimee-screen',
11 | html: `
12 |
13 |
24 |
25 |
26 |
37 |
38 | `,
39 | defaultEvent: {
40 | click: 'click'
41 | }
42 | };
43 |
44 | export default class Screen extends Base {
45 | constructor (parent, option) {
46 | super(parent);
47 | this.state = 'small';
48 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
49 | this.init();
50 | }
51 |
52 | init () {
53 | super.create();
54 | this.$dom = $(this.$dom);
55 | this.changeState(this.state);
56 | // addClassName(this.$dom, 'flex-item');
57 | this.$dom.addClass('chimee-flex-component');
58 |
59 | this.$full = this.$dom.find('chimee-screen-full');
60 | this.$small = this.$dom.find('chimee-screen-small');
61 | // 判断是否是默认或者用户提供 icon
62 | if(this.option.icon && this.option.icon.full && this.option.icon.small) {
63 | // if((!this.option.icon.play && this.option.icon.puase) || (this.option.icon.play && !this.option.icon.puase)) {
64 | // console.warn(`Please provide a play and pause icon!If you can't, we will use default icon!`);
65 | // }
66 | this.$full.html(this.option.icon.full);
67 | this.$small.html(this.option.icon.small);
68 | }else if(this.option.bitmap) {
69 | this.$full.html('');
70 | this.$small.html('');
71 | }
72 | }
73 |
74 | changeState (state) {
75 | const removeState = state === 'small' ? 'full' : 'small';
76 | addClassName(this.parent.$dom, state);
77 | removeClassName(this.parent.$dom, removeState);
78 | }
79 |
80 | click () {
81 | let full = false;
82 | if(this.state === 'small') {
83 | this.state = 'full';
84 | full = true;
85 | }else{
86 | this.state = 'small';
87 | full = false;
88 | }
89 | this.changeState(this.state);
90 | this.parent.$fullscreen(full, 'container');
91 | if(full) {
92 | this.watch_screen = this.parent.$watch('isFullscreen', this.screenChange);
93 | }else{
94 | this.watch_screen();
95 | }
96 | }
97 | @autobind
98 | screenChange () {
99 | if(!this.parent.fullscreenElement) return;
100 | this.state = 'small';
101 | this.changeState('small');
102 | this.parent.$fullscreen(false, 'container');
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/__test__/e2e/index.js:
--------------------------------------------------------------------------------
1 | import Chimee from 'chimee';
2 | import chimeePluginControlbar from '../../src/index';
3 |
4 | document.body.innerHTML = `
5 |
6 | `;
7 | Chimee.install(chimeePluginControlbar);
8 |
9 | const player = new Chimee({
10 | wrapper: '#wrap',
11 | src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_371ab0c0fda-143d-4755-8727-d3cd12dce02d.mp4',
12 | isLive: false,
13 | majorColor: '#f00',
14 | hoverColor: '#00f',
15 | plugin: [
16 | {
17 | name: chimeePluginControlbar.name,
18 | majorColor: '#f00',
19 | hoverColor: '#fff',
20 | children: {
21 | play: {},
22 | progressTime: true,
23 | progressBar: {
24 | layout: 'top' // baseline
25 | },
26 | volume: {
27 | icon: {
28 | low: `
29 |
39 | `,
40 | mute: `
41 |
52 | `,
53 | },
54 | layout: 'vertical'
55 | },
56 | clarity: [
57 | {name: '标清', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_368b70a5d4f-c5cc-42ff-b442-004168fc86a3.mp4'},
58 | {name: '高清', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_369ed890f51-1c38-42a7-9ce2-828492660c60.mp4'},
59 | {name: '原画', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_371ab0c0fda-143d-4755-8727-d3cd12dce02d.mp4'}
60 | ],
61 | screen: true
62 | }
63 | }
64 | ],
65 | controls: true,
66 | autoplay: true
67 | });
68 |
69 | const controls = player.$plugins.chimeeControl;
70 |
71 |
72 | describe('controls inited', async () => {
73 | expect(controls).toBeDefined();
74 | });
75 |
76 | describe('screen inited', async () => {
77 | const screen = await controls.children.screen;
78 | expect(screen).toBeDefined();
79 | });
80 |
--------------------------------------------------------------------------------
/coverage/chrome/sorter.js:
--------------------------------------------------------------------------------
1 | var addSorting = (function () {
2 | "use strict";
3 | var cols,
4 | currentSort = {
5 | index: 0,
6 | desc: false
7 | };
8 |
9 | // returns the summary table element
10 | function getTable() { return document.querySelector('.coverage-summary'); }
11 | // returns the thead element of the summary table
12 | function getTableHeader() { return getTable().querySelector('thead tr'); }
13 | // returns the tbody element of the summary table
14 | function getTableBody() { return getTable().querySelector('tbody'); }
15 | // returns the th element for nth column
16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
17 |
18 | // loads all columns
19 | function loadColumns() {
20 | var colNodes = getTableHeader().querySelectorAll('th'),
21 | colNode,
22 | cols = [],
23 | col,
24 | i;
25 |
26 | for (i = 0; i < colNodes.length; i += 1) {
27 | colNode = colNodes[i];
28 | col = {
29 | key: colNode.getAttribute('data-col'),
30 | sortable: !colNode.getAttribute('data-nosort'),
31 | type: colNode.getAttribute('data-type') || 'string'
32 | };
33 | cols.push(col);
34 | if (col.sortable) {
35 | col.defaultDescSort = col.type === 'number';
36 | colNode.innerHTML = colNode.innerHTML + '';
37 | }
38 | }
39 | return cols;
40 | }
41 | // attaches a data attribute to every tr element with an object
42 | // of data values keyed by column name
43 | function loadRowData(tableRow) {
44 | var tableCols = tableRow.querySelectorAll('td'),
45 | colNode,
46 | col,
47 | data = {},
48 | i,
49 | val;
50 | for (i = 0; i < tableCols.length; i += 1) {
51 | colNode = tableCols[i];
52 | col = cols[i];
53 | val = colNode.getAttribute('data-value');
54 | if (col.type === 'number') {
55 | val = Number(val);
56 | }
57 | data[col.key] = val;
58 | }
59 | return data;
60 | }
61 | // loads all row data
62 | function loadData() {
63 | var rows = getTableBody().querySelectorAll('tr'),
64 | i;
65 |
66 | for (i = 0; i < rows.length; i += 1) {
67 | rows[i].data = loadRowData(rows[i]);
68 | }
69 | }
70 | // sorts the table using the data for the ith column
71 | function sortByIndex(index, desc) {
72 | var key = cols[index].key,
73 | sorter = function (a, b) {
74 | a = a.data[key];
75 | b = b.data[key];
76 | return a < b ? -1 : a > b ? 1 : 0;
77 | },
78 | finalSorter = sorter,
79 | tableBody = document.querySelector('.coverage-summary tbody'),
80 | rowNodes = tableBody.querySelectorAll('tr'),
81 | rows = [],
82 | i;
83 |
84 | if (desc) {
85 | finalSorter = function (a, b) {
86 | return -1 * sorter(a, b);
87 | };
88 | }
89 |
90 | for (i = 0; i < rowNodes.length; i += 1) {
91 | rows.push(rowNodes[i]);
92 | tableBody.removeChild(rowNodes[i]);
93 | }
94 |
95 | rows.sort(finalSorter);
96 |
97 | for (i = 0; i < rows.length; i += 1) {
98 | tableBody.appendChild(rows[i]);
99 | }
100 | }
101 | // removes sort indicators for current column being sorted
102 | function removeSortIndicators() {
103 | var col = getNthColumn(currentSort.index),
104 | cls = col.className;
105 |
106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
107 | col.className = cls;
108 | }
109 | // adds sort indicators for current column being sorted
110 | function addSortIndicators() {
111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
112 | }
113 | // adds event listeners for all sorter widgets
114 | function enableUI() {
115 | var i,
116 | el,
117 | ithSorter = function ithSorter(i) {
118 | var col = cols[i];
119 |
120 | return function () {
121 | var desc = col.defaultDescSort;
122 |
123 | if (currentSort.index === i) {
124 | desc = !currentSort.desc;
125 | }
126 | sortByIndex(i, desc);
127 | removeSortIndicators();
128 | currentSort.index = i;
129 | currentSort.desc = desc;
130 | addSortIndicators();
131 | };
132 | };
133 | for (i =0 ; i < cols.length; i += 1) {
134 | if (cols[i].sortable) {
135 | // add the click event handler on the th so users
136 | // dont have to click on those tiny arrows
137 | el = getNthColumn(i).querySelector('.sorter').parentElement;
138 | if (el.addEventListener) {
139 | el.addEventListener('click', ithSorter(i));
140 | } else {
141 | el.attachEvent('onclick', ithSorter(i));
142 | }
143 | }
144 | }
145 | }
146 | // adds sorting functionality to the UI
147 | return function () {
148 | if (!getTable()) {
149 | return;
150 | }
151 | cols = loadColumns();
152 | loadData(cols);
153 | addSortIndicators();
154 | enableUI();
155 | };
156 | })();
157 |
158 | window.addEventListener('load', addSorting);
159 |
--------------------------------------------------------------------------------
/coverage/chrome/base.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | margin:0; padding: 0;
3 | height: 100%;
4 | }
5 | body {
6 | font-family: Helvetica Neue, Helvetica, Arial;
7 | font-size: 14px;
8 | color:#333;
9 | }
10 | .small { font-size: 12px; }
11 | *, *:after, *:before {
12 | -webkit-box-sizing:border-box;
13 | -moz-box-sizing:border-box;
14 | box-sizing:border-box;
15 | }
16 | h1 { font-size: 20px; margin: 0;}
17 | h2 { font-size: 14px; }
18 | pre {
19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20 | margin: 0;
21 | padding: 0;
22 | -moz-tab-size: 2;
23 | -o-tab-size: 2;
24 | tab-size: 2;
25 | }
26 | a { color:#0074D9; text-decoration:none; }
27 | a:hover { text-decoration:underline; }
28 | .strong { font-weight: bold; }
29 | .space-top1 { padding: 10px 0 0 0; }
30 | .pad2y { padding: 20px 0; }
31 | .pad1y { padding: 10px 0; }
32 | .pad2x { padding: 0 20px; }
33 | .pad2 { padding: 20px; }
34 | .pad1 { padding: 10px; }
35 | .space-left2 { padding-left:55px; }
36 | .space-right2 { padding-right:20px; }
37 | .center { text-align:center; }
38 | .clearfix { display:block; }
39 | .clearfix:after {
40 | content:'';
41 | display:block;
42 | height:0;
43 | clear:both;
44 | visibility:hidden;
45 | }
46 | .fl { float: left; }
47 | @media only screen and (max-width:640px) {
48 | .col3 { width:100%; max-width:100%; }
49 | .hide-mobile { display:none!important; }
50 | }
51 |
52 | .quiet {
53 | color: #7f7f7f;
54 | color: rgba(0,0,0,0.5);
55 | }
56 | .quiet a { opacity: 0.7; }
57 |
58 | .fraction {
59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60 | font-size: 10px;
61 | color: #555;
62 | background: #E8E8E8;
63 | padding: 4px 5px;
64 | border-radius: 3px;
65 | vertical-align: middle;
66 | }
67 |
68 | div.path a:link, div.path a:visited { color: #333; }
69 | table.coverage {
70 | border-collapse: collapse;
71 | margin: 10px 0 0 0;
72 | padding: 0;
73 | }
74 |
75 | table.coverage td {
76 | margin: 0;
77 | padding: 0;
78 | vertical-align: top;
79 | }
80 | table.coverage td.line-count {
81 | text-align: right;
82 | padding: 0 5px 0 20px;
83 | }
84 | table.coverage td.line-coverage {
85 | text-align: right;
86 | padding-right: 10px;
87 | min-width:20px;
88 | }
89 |
90 | table.coverage td span.cline-any {
91 | display: inline-block;
92 | padding: 0 5px;
93 | width: 100%;
94 | }
95 | .missing-if-branch {
96 | display: inline-block;
97 | margin-right: 5px;
98 | border-radius: 3px;
99 | position: relative;
100 | padding: 0 4px;
101 | background: #333;
102 | color: yellow;
103 | }
104 |
105 | .skip-if-branch {
106 | display: none;
107 | margin-right: 10px;
108 | position: relative;
109 | padding: 0 4px;
110 | background: #ccc;
111 | color: white;
112 | }
113 | .missing-if-branch .typ, .skip-if-branch .typ {
114 | color: inherit !important;
115 | }
116 | .coverage-summary {
117 | border-collapse: collapse;
118 | width: 100%;
119 | }
120 | .coverage-summary tr { border-bottom: 1px solid #bbb; }
121 | .keyline-all { border: 1px solid #ddd; }
122 | .coverage-summary td, .coverage-summary th { padding: 10px; }
123 | .coverage-summary tbody { border: 1px solid #bbb; }
124 | .coverage-summary td { border-right: 1px solid #bbb; }
125 | .coverage-summary td:last-child { border-right: none; }
126 | .coverage-summary th {
127 | text-align: left;
128 | font-weight: normal;
129 | white-space: nowrap;
130 | }
131 | .coverage-summary th.file { border-right: none !important; }
132 | .coverage-summary th.pct { }
133 | .coverage-summary th.pic,
134 | .coverage-summary th.abs,
135 | .coverage-summary td.pct,
136 | .coverage-summary td.abs { text-align: right; }
137 | .coverage-summary td.file { white-space: nowrap; }
138 | .coverage-summary td.pic { min-width: 120px !important; }
139 | .coverage-summary tfoot td { }
140 |
141 | .coverage-summary .sorter {
142 | height: 10px;
143 | width: 7px;
144 | display: inline-block;
145 | margin-left: 0.5em;
146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147 | }
148 | .coverage-summary .sorted .sorter {
149 | background-position: 0 -20px;
150 | }
151 | .coverage-summary .sorted-desc .sorter {
152 | background-position: 0 -10px;
153 | }
154 | .status-line { height: 10px; }
155 | /* dark red */
156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
157 | .low .chart { border:1px solid #C21F39 }
158 | /* medium red */
159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
160 | /* light red */
161 | .low, .cline-no { background:#FCE1E5 }
162 | /* light green */
163 | .high, .cline-yes { background:rgb(230,245,208) }
164 | /* medium green */
165 | .cstat-yes { background:rgb(161,215,106) }
166 | /* dark green */
167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
168 | .high .chart { border:1px solid rgb(77,146,33) }
169 | /* dark yellow (gold) */
170 | .medium .chart { border:1px solid #f9cd0b; }
171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
172 | /* light yellow */
173 | .medium { background: #fff4c2; }
174 | /* light gray */
175 | span.cline-neutral { background: #eaeaea; }
176 |
177 | .cbranch-no { background: yellow !important; color: #111; }
178 |
179 | .cstat-skip { background: #ddd; color: #111; }
180 | .fstat-skip { background: #ddd; color: #111 !important; }
181 | .cbranch-skip { background: #ddd !important; color: #111; }
182 |
183 |
184 | .cover-fill, .cover-empty {
185 | display:inline-block;
186 | height: 12px;
187 | }
188 | .chart {
189 | line-height: 0;
190 | }
191 | .cover-empty {
192 | background: white;
193 | }
194 | .cover-full {
195 | border-right: none !important;
196 | }
197 | pre.prettyprint {
198 | border: none !important;
199 | padding: 0 !important;
200 | margin: 0 !important;
201 | }
202 | .com { color: #999 !important; }
203 | .ignore-none { color: #999; font-weight: normal; }
204 |
205 | .wrapper {
206 | min-height: 100%;
207 | height: auto !important;
208 | height: 100%;
209 | margin: 0 auto -48px;
210 | }
211 | .footer, .push {
212 | height: 48px;
213 | }
214 |
--------------------------------------------------------------------------------
/src/progressbar.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, formatTime, $, addEvent, removeEvent, setStyle} from 'chimee-helper';
2 | import {autobind} from 'toxic-decorators';
3 | import Base from './base.js';
4 |
5 | const defaultOption = {
6 | tag: 'chimee-progressbar',
7 | html: `
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | `
17 | };
18 |
19 | export default class ProgressBar extends Base {
20 | constructor (parent, option) {
21 | super(parent);
22 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
23 | this.visiable = option !== false;
24 | this.init();
25 | }
26 |
27 | init () {
28 | super.create();
29 | this.$dom = $(this.$dom);
30 | this.$wrap = this.$dom.find('chimee-progressbar-wrap');
31 | this.$buffer = this.$dom.find('chimee-progressbar-buffer');
32 | this.$all = this.$dom.find('chimee-progressbar-all');
33 | this.$tip = this.$dom.find('chimee-progressbar-tip');
34 | this.$track = this.$dom.find('chimee-progressbar-track');
35 | this.$line = this.$dom.find('.chimee-progressbar-line');
36 | this.$ball = this.$dom.find('chimee-progressbar-ball');
37 | this.$dom.addClass('chimee-flex-component');
38 |
39 | // css 配置
40 | !this.visiable && this.$dom.css('visibility', 'hidden');
41 | // this.$line.css({
42 | // top: this.$wrap.
43 | // });
44 | // 进度条居中布局,还是在上方
45 | if(this.option.layout === 'top') {
46 | this.$dom.addClass('progressbar-layout-top');
47 | this.$wrap.css({
48 | // left: -this.$dom[0].offsetLeft + 'px',
49 | top: -this.$ball[0].offsetHeight + 'px',
50 | // height: this.$ball[0].offsetHeight * 2 + 'px'
51 | });
52 | // this.$line.css({
53 | // top: this.$ball[0].offsetHeight + 'px'
54 | // })
55 | setStyle(this.parent.$wrap, 'paddingTop', this.$ball[0].offsetHeight + 'px');
56 | }
57 | this.addWrapEvent();
58 | }
59 | destroy () {
60 | this.removeWrapEvent();
61 | // 解绑全屏监听事件
62 | this.watch_screen && this.watch_screen();
63 | super.destroy();
64 | }
65 | addWrapEvent () {
66 | this.$wrap.on('mousedown', this.mousedown);
67 | this.$wrap.on('mousemove', this.tipShow);
68 | this.$wrap.on('mouseleave', this.tipEnd);
69 | }
70 | removeWrapEvent () {
71 | this.$wrap.off('mousedown', this.mousedown);
72 | this.$wrap.off('mousemove', this.tipShow);
73 | this.$wrap.off('mouseleave', this.tipEnd);
74 | }
75 |
76 | /**
77 | * 缓存进度条更新 progress 事件
78 | */
79 | progress () {
80 | let buffer = 0;
81 | try{
82 | buffer = this.parent.buffered.end(0);
83 | }catch (e) {}
84 | const bufferWidth = buffer / this.parent.duration * 100 + '%';
85 | this.$buffer.css('width', bufferWidth);
86 | }
87 |
88 | /**
89 | * requestAnimationFrame 来更新进度条, timeupdate 事件
90 | */
91 | update () {
92 | // const allWidth = this.$wrap[0].offsetWidth - this.$ball[0].offsetWidth;
93 | const time = this._currentTime !== undefined ? this._currentTime : this.parent.currentTime;
94 | const timePer = time ? time / this.parent.duration : 0;
95 | // const timeWidth = timePer * allWidth;
96 | this.$all.css('width', `calc(${timePer * 100}% - ${this.$ball[0].offsetWidth / 2}px`);
97 | }
98 | @autobind
99 | mousedown (e) {
100 | // const ballRect = this.$ball[0].getClientRects()[0];
101 | // const ballLeft = ballRect.left;
102 | // const ballRight = ballRect.left + ballRect.width;
103 | // this.inBall = e.clientX <= ballRight && e.clientX >= ballLeft;
104 | if(e.target === this.$tip[0]) return;
105 |
106 | // return when duration is NaN chimee#129
107 | if(this.parent.duration !== this.parent.duration) {
108 | console.error('isNaN(duration) === true');
109 | return;
110 | }
111 | this._currentTime = e.offsetX / this.$wrap[0].offsetWidth * this.parent.duration;
112 | // if(!this.inBall) this.update();
113 | this.startX = e.clientX;
114 | this.startTime = this._currentTime;
115 | addEvent(window, 'mousemove', this.draging);
116 | addEvent(window, 'mouseup', this.dragEnd);
117 | addEvent(window, 'contextmenu', this.dragEnd);
118 | }
119 |
120 | /**
121 | * 开始拖拽
122 | * @param {EventObject} e 鼠标事件
123 | */
124 | @autobind
125 | draging (e) {
126 | this.endX = e.clientX;
127 | const dragTime = (this.endX - this.startX) / this.$wrap[0].offsetWidth * this.parent.duration;
128 | const dragAfterTime = +(this.startTime + dragTime).toFixed(2);
129 | this._currentTime = dragAfterTime < 0 ? 0 : dragAfterTime > this.parent.duration ? this.parent.duration : dragAfterTime;
130 | this.update();
131 | }
132 |
133 | /**
134 | * 结束拖拽
135 | */
136 | @autobind
137 | dragEnd () {
138 | this.update();
139 | this.startX = 0;
140 | this.startTime = 0;
141 | // if(!this.inBall) {
142 | this.parent.currentTime = this._currentTime;
143 | // this.inBall = false;
144 | // }
145 | this._currentTime = undefined;
146 | removeEvent(window, 'mousemove', this.draging);
147 | removeEvent(window, 'mouseup', this.dragEnd);
148 | removeEvent(window, 'contextmenu', this.dragEnd);
149 | }
150 |
151 | @autobind
152 | tipShow (e) {
153 | if(e.target === this.$tip[0] || e.target === this.$ball[0]) {
154 | this.$tip.css('display', 'none');
155 | return;
156 | };
157 | let time = e.offsetX / this.$wrap[0].offsetWidth * this.parent.duration;
158 | time = time < 0 ? 0 : time > this.parent.duration ? this.parent.duration : time;
159 | const tipContent = formatTime(time);
160 | let left = e.offsetX - this.$tip[0].offsetWidth / 2;
161 | const leftBound = this.$wrap[0].offsetWidth - this.$tip[0].offsetWidth;
162 | left = left < 0 ? 0 : left > leftBound ? leftBound : left;
163 | this.$tip.text(tipContent);
164 | this.$tip.css('display', 'inline-block');
165 | this.$tip.css('left', `${left}px`);
166 | }
167 | @autobind
168 | tipEnd () {
169 | this.$tip.css('display', 'none');
170 | }
171 |
172 | changePointerEvent (value) {
173 | this.$wrap.css('pointerEvents', value);
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/volume.js:
--------------------------------------------------------------------------------
1 | import {deepAssign, isObject, $, addEvent, removeEvent} from 'chimee-helper';
2 | import {autobind} from 'toxic-decorators';
3 | import Base from './base.js';
4 |
5 | /**
6 | * Volume 配置
7 | */
8 |
9 | const defaultOption = {
10 | tag: 'chimee-volume',
11 | html: `
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | `,
27 | animate: {
28 | icon: `
29 |
37 | `,
38 | },
39 | defaultEvent: {
40 | mousedown: 'mousedown'
41 | }
42 | };
43 |
44 | const getElementPath = function (elem) {
45 | const path = [];
46 | if(elem === null) return path;
47 | path.push(elem);
48 | while(elem.parentNode !== null) {
49 | elem = elem.parentNode;
50 | path.push(elem);
51 | };
52 | return path;
53 | };
54 |
55 | export default class Volume extends Base {
56 | constructor (parent, option) {
57 | super(parent);
58 | this.parent.preVolume = 0;
59 | this.option = deepAssign(defaultOption, isObject(option) ? option : {});
60 | this.init();
61 | }
62 |
63 | init () {
64 | super.create();
65 | this.$dom = $(this.$dom);
66 | this.$state = this.$dom.find('chimee-volume-state');
67 | this.$bar = this.$dom.find('chimee-volume-bar');
68 | this.$all = this.$dom.find('chimee-volume-bar-all');
69 | this.$bg = this.$dom.find('chimee-volume-bar-bg');
70 | this.layout = this.option.layout === 'vertical' ? 'vertical' : 'horizonal';
71 |
72 | // 判断是否是默认或者用户提供 icon
73 | if(this.option.icon && this.option.icon.mute && this.option.icon.low) {
74 | this.option.icon.high = this.option.icon.high || this.option.icon.low;
75 | this.$mute = this.$dom.find('chimee-volume-state-mute');
76 | this.$low = this.$dom.find('chimee-volume-state-low');
77 | this.$high = this.$dom.find('chimee-volume-state-high');
78 | this.$mute.html(this.option.icon.mute);
79 | this.$low.html(this.option.icon.low);
80 | this.$high.html(this.option.icon.high);
81 | }else if(!this.option.bitmap) {
82 | this.animate = true;
83 | this.$state.html(this.option.animate.icon);
84 | }
85 |
86 | this.$dom.addClass(`chimee-flex-component ${this.layout}`);
87 | this.changeState();
88 |
89 | this.watch_muted = this.parent.$watch('muted', (val) => {
90 | this.update();
91 | });
92 | }
93 |
94 | inited () {
95 | this.update();
96 | }
97 |
98 | destroy () {
99 | this.watch_muted();
100 | super.destroy();
101 | }
102 |
103 | changeState () {
104 | if(this.parent.muted || this.parent.volume === 0) {
105 | this.state = 'mute';
106 | }else if(this.parent.volume > 0 && this.parent.volume <= 0.5) {
107 | this.state = 'low';
108 | }else if(this.parent.volume > 0.5 && this.parent.volume <= 1) {
109 | this.state = 'high';
110 | }
111 | this.$dom.removeClass('mute low high');
112 | this.$dom.addClass(this.state);
113 | }
114 |
115 | click (e) {
116 | const path = e.path || getElementPath(e.target);
117 | if(path.indexOf(this.$state[0]) !== -1) {
118 | this.stateClick(e);
119 | return 'state';
120 | }else if(path.indexOf(this.$bar[0]) !== -1) {
121 | this.barClick(e);
122 | return 'bar';
123 | }
124 | return 'padding';
125 | }
126 |
127 | stateClick () {
128 | if(this.parent.muted) {
129 | this.parent.muted = false;
130 | return;
131 | }
132 | const currentVolume = this.parent.volume;
133 | this.parent.volume = currentVolume === 0 ? this.parent.preVolume : 0;
134 | this.parent.preVolume = currentVolume;
135 | this.changeState();
136 | }
137 |
138 | barClick (e) {
139 | const volume = this.layout === 'vertical'
140 | ? 1 - e.offsetY / this.$bg[0].offsetHeight
141 | : e.offsetX / this.$bg[0].offsetWidth;
142 | this.parent.volume = volume < 0 ? 0 : volume > 1 ? 1 : volume;
143 | this.update();
144 | }
145 |
146 | mousedown (e) {
147 | if(this.click(e) !== 'bar') return;
148 | this.startX = this.layout === 'vertical' ? e.clientY : e.clientX;
149 | this.startVolume = this.parent.volume;
150 | addEvent(window, 'mousemove', this.draging);
151 | addEvent(window, 'mouseup', this.dragEnd);
152 | addEvent(window, 'contextmenu', this.dragEnd);
153 | }
154 |
155 | /**
156 | * 更新声音条
157 | */
158 | update () {
159 | this.changeState();
160 | const volume = this.parent.muted ? 0 : this.parent.volume;
161 | this.layout === 'vertical' ? this.$all.css('height', `${volume * 100}%`) : this.$all.css('width', `${volume * 100}%`);
162 | }
163 |
164 | /**
165 | * 开始拖拽
166 | * @param {EventObject} e 鼠标事件
167 | */
168 | @autobind
169 | draging (e) {
170 | this.endX = this.layout === 'vertical' ? e.clientY : e.clientX;
171 | const dragVolume = this.layout === 'vertical' ? (this.startX - this.endX) / this.$bg[0].offsetHeight : (this.endX - this.startX) / this.$bg[0].offsetWidth;
172 | const dragAfterVolume = +(this.startVolume + dragVolume).toFixed(2);
173 | this.parent.volume = dragAfterVolume < 0 ? 0 : dragAfterVolume > 1 ? 1 : dragAfterVolume;
174 | this.parent.muted = false;
175 | }
176 |
177 | /**
178 | * 结束拖拽
179 | */
180 | @autobind
181 | dragEnd () {
182 | this.startX = 0;
183 | this.startVolume = 0;
184 | removeEvent(window, 'mousemove', this.draging);
185 | removeEvent(window, 'mouseup', this.dragEnd);
186 | removeEvent(window, 'contextmenu', this.dragEnd);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './control.css';
2 | import {accessor, applyDecorators} from 'toxic-decorators';
3 | import {isObject, deepAssign, setStyle} from 'chimee-helper';
4 | import {createChild} from './createchild.js';
5 | import { isArray } from 'toxic-predicate-functions';
6 |
7 | const majorColorStyle = `
8 | .chimee-flex-component svg * {
9 | fill: majorColor;
10 | stroke: majorColor;
11 | }
12 |
13 | chimee-progressbar-all {
14 | background: majorColor;
15 | }
16 |
17 | chimee-volume.chimee-flex-component chimee-volume-bar-all {
18 | background: majorColor;
19 | }
20 |
21 | chimee-playbackrate-list li:hover,
22 | chimee-playbackrate-list li.active,
23 | chimee-clarity-list li:hover,
24 | chimee-clarity-list li.active {
25 | color: majorColor;
26 | }
27 | `;
28 |
29 | const hoverColorStyle = `
30 | .chimee-flex-component svg:hover *{
31 | fill: hoverColor;
32 | stroke: hoverColor;
33 | }
34 | `;
35 |
36 | /**
37 | * 插件默认配置
38 | */
39 |
40 | const defaultConfig = {
41 | hideBarTime: 2000, // hidebar 延迟时间, barShowByMouse 为 move 时有效,enter 时为0, 用户设置无效
42 | barShowByMouse: 'move', // 控制条显示由, move 还是 enter/leave 来控制显示/隐藏,
43 | };
44 |
45 | const chimeeControl = {
46 | name: 'chimeeControl',
47 | el: 'chimee-control',
48 | data: {
49 | children: {},
50 | show: false,
51 | disabled: true
52 | },
53 | level: 99,
54 | operable: false,
55 | penetrate: false,
56 | create () {},
57 | init (videoConfig) {
58 | },
59 | destroy () {
60 | window.clearTimeout(this.timeId);
61 | for(const i in this.children) {
62 | this.children[i].destroy();
63 | }
64 | },
65 | inited () {
66 | const videoConfig = this.$videoConfig;
67 | if(videoConfig.controls) {
68 | this.show = true;
69 | videoConfig.controls = false;
70 | }
71 | const _this = this;
72 | applyDecorators(videoConfig, {
73 | controls: accessor({
74 | get () {
75 | return _this.show;
76 | },
77 | set (value) {
78 | _this.show = Boolean(value);
79 | _this._display();
80 | return false;
81 | }
82 | }, {preSet: true})
83 | }, {self: true});
84 | this.config = isObject(this.$config) ? deepAssign(defaultConfig, this.$config) : defaultConfig;
85 |
86 | this.config.hideBarTime = this.config.barShowByMouse === 'move' ? this.config.hideBarTime : 0;
87 | this.$dom.innerHTML = '';
88 | this.$wrap = this.$dom.querySelector('chimee-control-wrap');
89 | this.children = createChild(this);
90 | this._setStyle();
91 |
92 | for(const i in this.children) {
93 | this.children[i].inited && this.children[i].inited();
94 | }
95 | },
96 | events: {
97 | load () {
98 | // update src 充值进度条/时间/播放状态
99 | // load 的时候不会触发 pause(), 手动将控制条显示出来
100 | this._showItself();
101 | this._progressUpdate();
102 | this.children.play && this.children.play.changeState('pause');
103 | this.children.progressTime && this.children.progressTime.updateTotal();
104 | this.children.progressBar && this.children.progressBar.changePointerEvent('none');
105 | },
106 | loadstart () {
107 | this._disable(true);
108 | },
109 | canplay () {
110 | this._disable(false);
111 | },
112 | play () {
113 | this.children.play && this.children.play.changeState('play');
114 | this.config.barShowByMouse === 'move' && this._hideItself();
115 | },
116 | pause () {
117 | this.children.play && this.children.play.changeState('pause');
118 | this._showItself();
119 | },
120 | c_mouseenter () {
121 | if(this.config.barShowByMouse === 'move') return;
122 | this._showItself();
123 | },
124 | c_mousemove () {
125 | this._mousemove();
126 | },
127 | c_mouseleave () {
128 | if(this.config.barShowByMouse === 'move') return;
129 | this._hideItself();
130 | },
131 | durationchange () {
132 | this.children.progressTime && this.children.progressTime.updateTotal();
133 | this.children.progressBar && this.children.progressBar.changePointerEvent('auto');
134 | },
135 | timeupdate () {
136 | this._progressUpdate();
137 | },
138 | progress () {
139 | this.children.progressBar && this.children.progressBar.progress();
140 | },
141 | volumechange () {
142 | this.children.volume && this.children.volume.update();
143 | },
144 | keydown (e) {
145 | // if(this.disabled) return;
146 | e.stopPropagation();
147 | switch (e.keyCode) {
148 | case 32: {
149 | e.preventDefault();
150 | this.children.play && this.children.play.click(e);
151 | break;
152 | }
153 | case 37: {
154 | e.preventDefault();
155 | const reduceTime = this.currentTime - 10;
156 | this.currentTime = reduceTime < 0 ? 0 : reduceTime;
157 | this._mousemove();
158 | break;
159 | }
160 | case 39: {
161 | e.preventDefault();
162 | const raiseTime = this.currentTime + 10;
163 | this.currentTime = raiseTime > this.duration ? this.duration : raiseTime;
164 | this._mousemove();
165 | break;
166 | }
167 | case 38: {
168 | e.preventDefault();
169 | const raiseVolume = this.volume + 0.1;
170 | this.volume = raiseVolume > 1 ? 1 : raiseVolume;
171 | this._mousemove();
172 | break;
173 | }
174 | case 40: {
175 | e.preventDefault();
176 | const reduceVolume = this.volume - 0.1;
177 | this.volume = reduceVolume < 0 ? 0 : reduceVolume;
178 | this._mousemove();
179 | break;
180 | }
181 | }
182 | },
183 | click (e) {
184 | const time = new Date();
185 | const preTime = this.clickTime;
186 | this.clickTime = time;
187 | if(time - preTime < 300) {
188 | clearTimeout(this.clickTimeId);
189 | return;
190 | }
191 | this.clickTimeId = setTimeout(() => {
192 | !this.disabled && this.children.play && this.children.play.click(e);
193 | }, 300);
194 |
195 | },
196 | dblclick (e) {
197 | // this.dblclick = true;
198 | !this.disabled && this.children.screen && this.children.screen.click();
199 | }
200 | },
201 | methods: {
202 | _progressUpdate () {
203 | this.children.progressBar && this.children.progressBar.update();
204 | this.children.progressTime && this.children.progressTime.updatePass();
205 | },
206 | _hideItself () {
207 | window.clearTimeout(this.timeId);
208 | this.timeId = setTimeout(() => {
209 | let bottom = this.$wrap.offsetHeight;
210 | bottom = this.children.progressBar ? this.children.progressBar.$wrap[0].offsetTop - bottom : -bottom;
211 | setStyle(this.$wrap, {
212 | bottom: bottom + 'px'
213 | });
214 | setStyle(this.$dom, {
215 | visibility: 'hidden'
216 | });
217 | }, this.config.hideBarTime);
218 | },
219 | _showItself () {
220 | window.clearTimeout(this.timeId);
221 | setStyle(this.$wrap, {
222 | bottom: '0'
223 | });
224 | setStyle(this.$dom, {
225 | visibility: 'visible'
226 | });
227 | },
228 | _display () {
229 | const display = this.show ? 'block' : 'none';
230 | setStyle(this.$dom, {
231 | display
232 | });
233 | },
234 | _mousemove (e) {
235 | if(this.paused || this.config.barShowByMouse === 'enter') return;
236 | this._showItself();
237 | this._hideItself();
238 | },
239 | // controlbar 不可以点
240 | // 键盘/鼠标事件不监听
241 | _disable (disabled) {
242 | if(!this.show) return;
243 | this.disabled = disabled;
244 | // setStyle(this.$wrap, 'pointerEvents', disabled ? 'none' : 'auto');
245 | },
246 | _setStyle () {
247 | let css = '';
248 | css += this.config.majorColor ? majorColorStyle.replace(/majorColor/g, this.config.majorColor) : '';
249 | css += this.config.hoverColor ? hoverColorStyle.replace(/hoverColor/g, this.config.hoverColor) : '';
250 | const style = document.createElement('style');
251 | style.setAttribute('type', 'text/css');
252 | style.innerHTML = css;
253 | document.head.appendChild(style);
254 | },
255 | updateClarity (list) {
256 | if(!isArray(list)) {
257 | return console.error('list must be an array.');
258 | }
259 | this.children.clarity.initTextList(list);
260 | }
261 | }
262 | };
263 |
264 | export default chimeeControl;
265 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "process": true,
4 | "test": true,
5 | "expect": true,
6 | "describe": true,
7 | "it": true
8 | },
9 | "env": {
10 | "browser": true,
11 | "commonjs": true,
12 | "es6": true,
13 | "jasmine": true
14 | },
15 | "extends": [
16 | ],
17 | "plugins": [
18 | ],
19 | "parser": "babel-eslint",
20 | "parserOptions": {
21 | "sourceType": "module"
22 | },
23 | "rules": {
24 | // 强制getter/setter成对出现在对象中
25 | "accessor-pairs": 2,
26 | // 禁止在括号内使用空格
27 | "array-bracket-spacing": 0,
28 | // 把 var 语句看作是在块级作用域范围之内
29 | "block-scoped-var": 0,
30 | // 大括号风格
31 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
32 | // 不强制要求使用骆驼拼写法
33 | "camelcase": 0,
34 | // 强制在逗号周围使用空格
35 | "comma-spacing": [2, { "before": false, "after": true }],
36 | // 逗号风格(必须要放在末尾)
37 | "comma-style": [2, "last"],
38 | // 限制圈复杂度(条件数目,0为不作限制)
39 | "complexity": 0,
40 | // 禁止在计算属性内使用空格
41 | "computed-property-spacing": 0,
42 | // 允许返回不指定的值
43 | "consistent-return": 0,
44 | // 不要求一致的 This
45 | "consistent-this": 0,
46 | // 验证构造函数中 super() 的调用
47 | "constructor-super": 2,
48 | // 多行是必须遵循大括号约定
49 | "curly": [2, "multi-line"],
50 | // 不强制要求含有default分支
51 | "default-case": 0,
52 | // 强制在点号之前换行
53 | "dot-location": [2, "property"],
54 | // 不强制要求使用点号
55 | "dot-notation": 0,
56 | // 要求文件末尾保留一行空行
57 | "eol-last": 2,
58 | // 要求使用全等号
59 | "eqeqeq": [2, "allow-null"],
60 | // 可以使用匿名函数
61 | "func-names": 0,
62 | // 不强制 function 声明或表达式的一致性
63 | "func-style": 0,
64 | // 强制 generator 函数中 * 号周围有空格
65 | "generator-star-spacing": [2, { "before": true, "after": true }],
66 | // 不要求 for-in 语句中包含 if
67 | "guard-for-in": 0,
68 | // 强制回调错误处理,必须要使用err或error。不处理则会报错
69 | "handle-callback-err": [2, "^(err|error)$" ],
70 | // 强制使用一致的缩进
71 | //"indent": [2, 2, { "SwitchCase": 1 }],
72 | // 强制在对象字面量的键和值之间使用一致的空格
73 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
74 | // 不强制使用一致的换行符风格
75 | "linebreak-style": 0,
76 | // 不强制注释周围有空行
77 | "lines-around-comment": 0,
78 | // 不强制回调函数最大嵌套深度
79 | "max-nested-callbacks": 0,
80 | // 要求构造函数首字母大写
81 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
82 | // 要求调用无参构造函数时带括号
83 | "new-parens": 2,
84 | // 不要求变量声明语句后有一行空行
85 | "newline-after-var": 0,
86 | // 不禁用alert
87 | "no-alert": 0,
88 | // 禁止使用 Array 构造函数
89 | "no-array-constructor": 2,
90 | // 禁用 caller 或 callee
91 | "no-caller": 2,
92 | // 不禁用在 catch 语句中遮盖变量
93 | "no-catch-shadow": 0,
94 | // 禁止在条件语句中出现赋值操作符
95 | "no-cond-assign": 2,
96 | // 禁止使用console符
97 | "no-console": 0,
98 | // 禁止在条件中使用常量表达式
99 | "no-constant-condition": 2,
100 | // 不禁用 continue
101 | "no-continue": 0,
102 | // 禁止在正则表达式中使用控制字符
103 | "no-control-regex": 2,
104 | // 禁用debugger
105 | // "no-debugger": 2,
106 | // 禁止删除变量
107 | "no-delete-var": 2,
108 | // 不禁止使用看起来像除法的正则表达式
109 | "no-div-regex": 0,
110 | // 禁止 function 定义中出现重名参数
111 | "no-dupe-args": 2,
112 | // 禁止对象字面量中出现重复的 key
113 | "no-dupe-keys": 2,
114 | // 禁止出现重复的 case 标签
115 | "no-duplicate-case": 2,
116 | // 不禁止 if 语句中 return 语句之后有 else 块
117 | "no-else-return": 0,
118 | // 不禁止出现空语句块
119 | "no-empty": 0,
120 | // 禁止在正则表达式中使用空字符集
121 | "no-empty-character-class": 2,
122 | // 禁止与 null 进行比较
123 | "no-eq-null": 2,
124 | // 禁止使用eval
125 | "no-eval": 2,
126 | // 禁止对 catch 子句中的异常重新赋值
127 | "no-ex-assign": 2,
128 | // 禁止扩展原生类型
129 | "no-extend-native": 2,
130 | // 禁止不必要的 .bind() 调用
131 | "no-extra-bind": 2,
132 | // 禁止不必要的布尔转换
133 | "no-extra-boolean-cast": 2,
134 | // 不禁止冗余的括号
135 | "no-extra-parens": 0,
136 | // 不禁止不必要的分号
137 | "no-extra-semi": 0,
138 | // 禁止 case 语句落空
139 | "no-fallthrough": 2,
140 | // 禁止数字字面量中使用前导和末尾小数点
141 | "no-floating-decimal": 2,
142 | // 禁止对 function 声明重新赋值
143 | "no-func-assign": 2,
144 | // 禁用隐式的eval()
145 | "no-implied-eval": 2,
146 | // 允许在代码后使用内联注释
147 | "no-inline-comments": 0,
148 | // 禁止 function 声明出现在嵌套的语句块中
149 | "no-inner-declarations": [2, "functions"],
150 | // 禁止 RegExp 构造函数中存在无效的正则表达式字符串
151 | "no-invalid-regexp": 2,
152 | // 禁止在字符串和注释之外不规则的空白
153 | "no-irregular-whitespace": 2,
154 | // 禁用迭代器
155 | "no-iterator": 2,
156 | // 不允许标签与变量同名
157 | "no-label-var": 2,
158 | // 禁用不必要的嵌套块
159 | "no-lone-blocks": 2,
160 | // 不禁止 if 作为唯一的语句出现在 else 语句中
161 | "no-lonely-if": 0,
162 | // 不禁止循环中存在函数
163 | "no-loop-func": 0,
164 | // 不禁止 require 调用与普通变量声明混合使用
165 | "no-mixed-requires": 0,
166 | // 禁止空格和 tab 的混合缩进
167 | //"no-mixed-spaces-and-tabs": 2,
168 | // 禁止出现多个空格
169 | "no-multi-spaces": 2,
170 | // 禁止多行字符串
171 | "no-multi-str": 2,
172 | // 不允许多个空行
173 | "no-multiple-empty-lines": [2, { "max": 1 }],
174 | "no-native-reassign": 2,
175 | "no-negated-in-lhs": 2,
176 | // 不禁止使用嵌套的三元表达式,但是要换行!!!
177 | "no-nested-ternary": 0,
178 | // 禁止在非赋值或条件语句中使用 new 操作符
179 | "no-new": 2,
180 | // 不禁止对 Function 对象使用 new 操作符
181 | "no-new-func": 0,
182 | // 禁用 Object 的构造函数
183 | "no-new-object": 2,
184 | // 禁止调用 require 时使用 new 操作符
185 | "no-new-require": 2,
186 | // 禁止对 String,Number 和 Boolean 使用 new 操作符
187 | "no-new-wrappers": 2,
188 | // 禁止把全局对象作为函数调用
189 | "no-obj-calls": 2,
190 | // 禁用八进制字面量
191 | "no-octal": 2,
192 | // 禁止在字符串中使用八进制转义序列
193 | "no-octal-escape": 2,
194 | // 不禁止对 function 的参数进行重新赋值
195 | "no-param-reassign": 0,
196 | // 不禁止对 __dirname 和 __filename 进行字符串连接
197 | "no-path-concat": 0,
198 | // 不禁用 process.env
199 | "no-process-env": 0,
200 | // 不禁用 process.exit()
201 | "no-process-exit": 0,
202 | // 不禁用禁用 __proto__ 属性
203 | "no-proto": 0,
204 | // 禁止重新声明变量
205 | "no-redeclare": 2,
206 | // 禁止正则表达式字面量中出现多个空格
207 | "no-regex-spaces": 2,
208 | // 不禁用通过 require 加载的指定模块
209 | "no-restricted-modules": 0,
210 | // 禁止在返回语句中赋值
211 | "no-return-assign": 2,
212 | // 不禁止使用 javascript: url
213 | "no-script-url": 0,
214 | // 禁止自身比较
215 | "no-self-compare": 2,
216 | // 不允许使用逗号操作符
217 | "no-sequences": 2,
218 | // 不禁止变量声明与外层作用域的变量同名
219 | "no-shadow": 0,
220 | // 关键字不能被遮蔽
221 | "no-shadow-restricted-names": 2,
222 | "no-spaced-func": 2,
223 | // 禁用稀疏数组
224 | "no-sparse-arrays": 2,
225 | // 不禁止使用同步方法
226 | "no-sync": 0,
227 | // 不禁用三元操作符
228 | "no-ternary": 0,
229 | // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
230 | "no-this-before-super": 2,
231 | // 禁止抛出异常字面量
232 | "no-throw-literal": 2,
233 | // 禁用行尾空格
234 | "no-trailing-spaces": 2,
235 | // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
236 | "no-undef": 2,
237 | // 禁止将变量初始化为 undefined
238 | "no-undef-init": 2,
239 | // 不禁止将 undefined 作为标识符
240 | "no-undefined": 0,
241 | // 允许标识符中有悬空下划线
242 | "no-underscore-dangle": 0,
243 | // 禁止出现令人困惑的多行表达式
244 | "no-unexpected-multiline": 2,
245 | // 禁止可以在有更简单的可替代的表达式时使用三元操作符
246 | "no-unneeded-ternary": 2,
247 | // 禁止在return、throw、continue 和 break 语句之后出现不可达代码
248 | "no-unreachable": 2,
249 | "no-unused-expressions": 0,
250 | // 禁止未使用过的变量
251 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
252 | // 不禁止定义前使用
253 | "no-use-before-define": 0,
254 | // 要求使用 let 或 const 而不是 var
255 | "no-var": 2,
256 | // 不禁用 void 操作符
257 | "no-void": 0,
258 | // 不禁用警告注释
259 | "no-warning-comments": 0,
260 | // 禁用with
261 | "no-with": 2,
262 | // 不强制在花括号中使用一致的空格
263 | "object-curly-spacing": 0,
264 | // 要求对象字面量简写语法
265 | "object-shorthand": 2,
266 | // 强制函数中的变量在一起声明
267 | "one-var": [2, { "initialized": "never" }],
268 | // 不要求或禁止尽可能地简化赋值操作
269 | "operator-assignment": 0,
270 | // 强制操作符使用一致的换行符
271 | "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
272 | // 不要求或禁止块内填充
273 | "padded-blocks": 0,
274 | // 建议使用const
275 | "prefer-const": 2,
276 | // 不要求对象字面量属性名称使用引号
277 | "quote-props": 0,
278 | // 引号
279 | "quotes": [2, "single", "avoid-escape"],
280 | // parseint输入10防止八进制
281 | "radix": 0,
282 | // 用分号
283 | "semi": 1,
284 | // 不强制分号之前和之后使用一致的空格
285 | "semi-spacing": 0,
286 | // 无需变量排序
287 | "sort-vars": 0,
288 | // 要求语句块之前的空格
289 | "space-before-blocks": [2, "always"],
290 | // 强制在 function的左括号之前使用一致的空格
291 | "space-before-function-paren": [2, "always"],
292 | // 强制在圆括号内使用一致的空格
293 | "space-in-parens": [2, "never"],
294 | // 要求操作符周围有空格
295 | "space-infix-ops": 2,
296 | // 强制在一元操作符前后使用一致的空格
297 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
298 | // 强制在注释中 // 或 /* 使用一致的空格
299 | "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
300 | // 不要求严格模式
301 | "strict": 0,
302 | // 要求使用 isNaN() 检查 NaN
303 | "use-isnan": 2,
304 | // 不强制使用有效的 JSDoc 注释
305 | "valid-jsdoc": 0,
306 | // 强制 typeof 表达式与有效的字符串进行比较
307 | "valid-typeof": 2,
308 | // 不要求所有的 var 声明出现在它们所在的作用域顶部
309 | "vars-on-top": 0,
310 | // 要求 IIFE 使用括号括起来
311 | "wrap-iife": [2, "any"],
312 | // 不要求正则表达式被括号括起来
313 | "wrap-regex": 0,
314 | // 比较时,变量值必须放在首位
315 | "yoda": [2, "never"]
316 | }
317 | }
--------------------------------------------------------------------------------
/src/control.css:
--------------------------------------------------------------------------------
1 | /* 暂时存放到这的, 用来设置 container video 的基本样式 */
2 | container {
3 | position: relative;
4 | }
5 |
6 | container,
7 | video {
8 | display: block;
9 | width: 100%;
10 | height: 100%;
11 | background: #000;
12 | outline: none;
13 | }
14 |
15 | video:focus {
16 | outline: none;
17 | }
18 |
19 | /* 用到的变量 */
20 | :root {
21 | --barColor: #de698c;
22 | --trackColor: #4c4c4c;
23 | }
24 |
25 | /* 全局默认样式 */
26 | .chimee-flex-component svg g {
27 | fill: #fff;
28 | stroke: #fff;
29 | }
30 |
31 | .chimee-flex-component svg:hover g {
32 | fill: #fff;
33 | stroke: #fff;
34 | }
35 |
36 | /* 默认隐藏 */
37 | chimee-volume-state-mute,
38 | chimee-volume-state-low,
39 | chimee-volume-state-high,
40 | chimee-control-state-pause,
41 | chimee-control-state-play,
42 | chimee-progressbar-tip,
43 | chimee-screen-full,
44 | chimee-playbackrate-list,
45 | chimee-clarity-list,
46 | chimee-screen-small {
47 | display: none;
48 | }
49 |
50 | /* 满足条件时显示 */
51 | chimee-control.full chimee-screen-full,
52 | chimee-control.small chimee-screen-small,
53 | chimee-volume.mute chimee-volume-state-mute,
54 | chimee-volume.low chimee-volume-state-low,
55 | chimee-volume.high chimee-volume-state-high,
56 | chimee-control.pause chimee-control-state-pause,
57 | chimee-control.play chimee-control-state-play,
58 | chimee-control.full chimee-screen-full,
59 | chimee-control.small chimee-screen-small {
60 | display: inline-block;
61 | width: 1.4em;
62 | height: 100%;
63 | }
64 |
65 | /* 开始写具体样式 */
66 | chimee-control {
67 | position: absolute;
68 | bottom: 0;
69 | left: 0;
70 | display: block;
71 | width: 100%;
72 | height: 100%;
73 | line-height: 2em;
74 | font-size: 14px;
75 | user-select: none;
76 | overflow: hidden;
77 | font-family: Roboto, Arial, Helvetica, sans-serif;
78 | transition: visibility 0.5s ease;
79 | }
80 |
81 | chimee-control-wrap {
82 | position: absolute;
83 | left: 0;
84 | bottom: 0;
85 | width: 100%;
86 | height: 2.2em;
87 | display: flex;
88 | flex-flow: row nowrap;
89 | justify-content: flex-start;
90 | align-items: flex-start;
91 | background: rgba(0, 0, 0, 0.5);
92 | transition: bottom 0.5s ease;
93 | pointer-events: auto;
94 | }
95 |
96 | .chimee-flex-component {
97 | order: 1;
98 | flex-grow: 0;
99 | height: 2em;
100 | cursor: pointer;
101 | }
102 |
103 | .chimee-flex-component svg {
104 | vertical-align: middle;
105 | width: 2em;
106 | height: 1.5em;
107 | }
108 |
109 | /* 播放器状态,播放/暂停 */
110 | chimee-control-state.chimee-flex-component {
111 | flex-basis: 3em;
112 | text-align: right;
113 | margin-right: 1em;
114 | }
115 |
116 | /* 播放器状态,播放/暂停 动画效果 */
117 | chimee-control-state .left,
118 | chimee-control-state .right {
119 | transition: d 0.2s ease-in-out;
120 | }
121 |
122 | /* 时间显示 */
123 | chimee-progresstime.chimee-flex-component {
124 | color: #fff;
125 | font-weight: normal;
126 | text-align: center;
127 | white-space: nowrap;
128 | }
129 |
130 | chimee-progresstime-pass,
131 | chimee-progresstime-total {
132 | display: inline;
133 | }
134 |
135 | /* 播放器控制条 */
136 | chimee-progressbar.chimee-flex-component {
137 | position: relative;
138 | flex-grow: 1;
139 | }
140 |
141 | chimee-progressbar-wrap {
142 | display: inline-block;
143 | height: 100%;
144 | position: absolute;
145 | left: 1em;
146 | right: 1em;
147 | top: 0;
148 | pointer-events: none;
149 | }
150 |
151 | chimee-progressbar.progressbar-layout-top {
152 | position: static;
153 |
154 | & chimee-progressbar-wrap {
155 | top: -1.6em;
156 | height: 1.6em;
157 | left: 0;
158 | right: 0;
159 | }
160 |
161 | & .chimee-progressbar-line {
162 | left: 0;
163 | top: 0.8em;
164 | }
165 | }
166 |
167 | .chimee-progressbar-line {
168 | position: absolute;
169 | top: 0.9em;
170 | left: 0;
171 | display: inline-block;
172 | height: 3px;
173 | }
174 |
175 | chimee-progressbar-bg {
176 | width: 100%;
177 | background: var(--trackColor);
178 | }
179 |
180 | chimee-progressbar-buffer {
181 | width: 0;
182 | background: #6f6f6f;
183 | }
184 |
185 | chimee-progressbar-all {
186 | background: var(--barColor);
187 | }
188 |
189 | chimee-progressbar-ball {
190 | content: '';
191 | position: absolute;
192 | right: -0.8em;
193 | top: -0.3em;
194 | display: inline-block;
195 | width: 0.8em;
196 | height: 0.8em;
197 | border-radius: 0.8em;
198 | background: #fff;
199 | pointer-events: none;
200 | }
201 |
202 | chimee-progressbar-tip {
203 | position: absolute;
204 | bottom: 0.5em;
205 | top: -1.5em;
206 | left: 0;
207 | z-index: 10;
208 | padding: 0 0.5em;
209 | height: 1.5em;
210 | background: #fff;
211 | line-height: 1.5em;
212 | border-radius: 4px;
213 | color: #000;
214 | text-align: center;
215 | }
216 |
217 | /* 音量控制 */
218 | chimee-volume.chimee-flex-component {
219 | cursor: pointer;
220 | padding: 0;
221 | flex-basis: 7em;
222 | white-space: nowrap;
223 | margin-right: 1em;
224 | position: relative;
225 | }
226 |
227 | chimee-volume.chimee-flex-component.vertical {
228 | padding-right: 1em;
229 | flex-basis: 2em;
230 | }
231 |
232 | chimee-volume-state {
233 | display: inline-block;
234 | width: 2em;
235 | vertical-align: top;
236 | }
237 |
238 | /* 动画所用到的元素 css */
239 | chimee-volume .ring1,
240 | chimee-volume .ring2,
241 | chimee-volume .line {
242 | stroke-dasharray: 150;
243 | stroke-dashoffset: 150;
244 | transition: stroke-dashoffset 0.7s ease-in-out;
245 | }
246 |
247 | chimee-volume.mute .line,
248 | chimee-volume.mute .ring1,
249 | chimee-volume.mute .ring2 {
250 | stroke-dashoffset: 0;
251 | }
252 |
253 | chimee-volume.high .ring1,
254 | chimee-volume.high .ring2 {
255 | stroke-dashoffset: 0;
256 | }
257 |
258 | chimee-volume.low .ring2 {
259 | stroke-dashoffset: 0;
260 | }
261 |
262 | chimee-volume.vertical:hover chimee-volume-bar {
263 | display: inline-block;
264 | }
265 |
266 | chimee-volume.vertical chimee-volume-bar {
267 | position: absolute;
268 | top: -7em;
269 | left: -0.2em;
270 | width: 2em;
271 | height: 4em;
272 | padding-bottom: 3em;
273 | display: none;
274 | vertical-align: middle;
275 | }
276 |
277 | chimee-volume.vertical chimee-volume-bar::before {
278 | content: '';
279 | position: absolute;
280 | left: 0;
281 | right: 0;
282 | top: 0;
283 | bottom: 1em;
284 | background: #212121;
285 | border-radius: 4px;
286 | }
287 |
288 | chimee-volume.horizonal chimee-volume-bar {
289 | position: relative;
290 | height: 1.2em;
291 | width: 4em;
292 | display: inline-block;
293 | vertical-align: middle;
294 | }
295 |
296 | chimee-volume.vertical chimee-volume-bar-wrap {
297 | display: inline-block;
298 | position: absolute;
299 | bottom: 1em;
300 | left: 0;
301 | top: 1em;
302 | right: 0;
303 | height: 4em;
304 | }
305 |
306 | chimee-volume.vertical chimee-volume-bar-all,
307 | chimee-volume.vertical chimee-volume-bar-bg {
308 | position: absolute;
309 | bottom: 0;
310 | left: 1em;
311 | display: inline-block;
312 | width: 2px;
313 | border-radius: 4px;
314 | }
315 |
316 | chimee-volume.vertical chimee-volume-bar-bg {
317 | height: 4em;
318 | background: var(--trackColor);
319 | opacity: 0.5;
320 | }
321 |
322 | chimee-volume.vertical chimee-volume-bar-all {
323 | background: var(--barColor);
324 | }
325 |
326 | chimee-volume.vertical chimee-volume-bar-all::after {
327 | content: '';
328 | position: absolute;
329 | right: -0.34em;
330 | top: -0.4em;
331 | display: inline-block;
332 | width: 0.8em;
333 | height: 0.8em;
334 | border-radius: 0.8em;
335 | background: #fff;
336 | pointer-events: none;
337 | }
338 |
339 | chimee-volume.horizonal chimee-volume-bar {
340 | position: relative;
341 | height: 1.2em;
342 | width: 4em;
343 | display: inline-block;
344 | vertical-align: middle;
345 | }
346 |
347 | chimee-volume.horizonal chimee-volume-bar-all,
348 | chimee-volume.horizonal chimee-volume-bar-bg {
349 | position: absolute;
350 | top: 0.4em;
351 | left: 0;
352 | display: inline-block;
353 | height: 2px;
354 | }
355 |
356 | chimee-volume.horizonal chimee-volume-bar-bg {
357 | width: 4em;
358 | background: var(--trackColor);
359 | opacity: 0.5;
360 | }
361 |
362 | chimee-volume.horizonal chimee-volume-bar-all {
363 | background: var(--barColor);
364 | }
365 |
366 | chimee-volume.horizonal chimee-volume-bar-all::after {
367 | content: '';
368 | position: absolute;
369 | right: -0.4em;
370 | top: -0.3em;
371 | display: inline-block;
372 | width: 0.8em;
373 | height: 0.8em;
374 | border-radius: 0.8em;
375 | background: #fff;
376 | pointer-events: none;
377 | }
378 |
379 | /* 全屏 */
380 | chimee-screen.chimee-flex-component {
381 | flex-basis: 3em;
382 | text-align: left;
383 | }
384 |
385 | /* 清晰度切换, 倍速切换 */
386 | chimee-clarity.chimee-flex-component,
387 | chimee-playbackrate.chimee-flex-component {
388 | position: relative;
389 | color: #fff;
390 | width: 6em;
391 | height: 1.75em;
392 | padding: 0;
393 | padding-right: 1em;
394 | padding-left: 1em;
395 | text-align: center;
396 | vertical-align: middle;
397 | font-size: 16px;
398 | }
399 |
400 | chimee-playbackrate.chimee-flex-component:hover chimee-playbackrate-list,
401 | chimee-clarity.chimee-flex-component:hover chimee-clarity-list {
402 | display: inline-block;
403 | }
404 |
405 | chimee-playbackrate.chimee-flex-component svg,
406 | chimee-clarity.chimee-flex-component svg {
407 | width: auto;
408 | height: auto;
409 | }
410 |
411 | chimee-playbackrate-list,
412 | chimee-clarity-list {
413 | position: absolute;
414 | left: 0;
415 | bottom: 1em;
416 | width: 100%;
417 | padding-bottom: 1.5em;
418 | opacity: 0.8;
419 | box-sizing: content-box;
420 | line-height: 0;
421 | }
422 |
423 | chimee-playbackrate-list ul,
424 | chimee-clarity-list ul {
425 | margin: 0;
426 | padding: 0.5em 0;
427 | background: #292929;
428 | line-height: 2em;
429 | }
430 |
431 | chimee-playbackrate-list li,
432 | chimee-clarity-list li {
433 | list-style-type: none;
434 | }
435 |
436 | chimee-playbackrate-list li:hover,
437 | chimee-playbackrate-list li.active,
438 | chimee-clarity-list li:hover,
439 | chimee-clarity-list li.active {
440 | color: var(--barColor);
441 | }
442 |
443 | chimee-playbackrate-list-arrow,
444 | chimee-clarity-list-arrow {
445 | position: absolute;
446 | bottom: 1.5em;
447 | width: 100%;
448 | }
449 |
--------------------------------------------------------------------------------
/coverage/chrome/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for src/
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 34.77%
24 | Statements
25 | 7788/22400
26 |
27 |
28 | 17.65%
29 | Branches
30 | 2659/15064
31 |
32 |
33 | 24.41%
34 | Functions
35 | 1115/4568
36 |
37 |
38 | 36.51%
39 | Lines
40 | 7531/20628
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | | File |
50 | |
51 | Statements |
52 | |
53 | Branches |
54 | |
55 | Functions |
56 | |
57 | Lines |
58 | |
59 |
60 |
61 |
62 | | base.js |
63 | |
64 | 35.55% |
65 | 600/1688 |
66 | 19.4% |
67 | 218/1124 |
68 | 22.16% |
69 | 76/343 |
70 | 37.49% |
71 | 580/1547 |
72 |
73 |
74 |
75 | | clarity.js |
76 | |
77 | 35.76% |
78 | 664/1857 |
79 | 18.58% |
80 | 225/1211 |
81 | 23.38% |
82 | 90/385 |
83 | 37.51% |
84 | 641/1709 |
85 |
86 |
87 |
88 | | component.js |
89 | |
90 | 36.58% |
91 | 653/1785 |
92 | 19.05% |
93 | 225/1181 |
94 | 24.32% |
95 | 89/366 |
96 | 38.49% |
97 | 630/1637 |
98 |
99 |
100 |
101 | | createchild.js |
102 | |
103 | 33.22% |
104 | 949/2857 |
105 | 16.52% |
106 | 316/1913 |
107 | 25% |
108 | 143/572 |
109 | 34.72% |
110 | 920/2650 |
111 |
112 |
113 |
114 | | index.js |
115 | |
116 | 32.08% |
117 | 966/3011 |
118 | 15.78% |
119 | 319/2021 |
120 | 23.72% |
121 | 144/607 |
122 | 33.58% |
123 | 937/2790 |
124 |
125 |
126 |
127 | | play.js |
128 | |
129 | 35.97% |
130 | 663/1843 |
131 | 18.5% |
132 | 225/1216 |
133 | 23.81% |
134 | 90/378 |
135 | 37.76% |
136 | 640/1695 |
137 |
138 |
139 |
140 | | progressbar.js |
141 | |
142 | 34.58% |
143 | 877/2536 |
144 | 17.37% |
145 | 302/1739 |
146 | 25.34% |
147 | 131/517 |
148 | 36.36% |
149 | 848/2332 |
150 |
151 |
152 |
153 | | progresstime.js |
154 | |
155 | 36.24% |
156 | 665/1835 |
157 | 18.58% |
158 | 225/1211 |
159 | 23.75% |
160 | 90/379 |
161 | 38.06% |
162 | 642/1687 |
163 |
164 |
165 |
166 | | screen.js |
167 | |
168 | 35.38% |
169 | 875/2473 |
170 | 17.7% |
171 | 302/1706 |
172 | 25.84% |
173 | 131/507 |
174 | 37.27% |
175 | 846/2270 |
176 |
177 |
178 |
179 | | volume.js |
180 | |
181 | 34.83% |
182 | 876/2515 |
183 | 17.34% |
184 | 302/1742 |
185 | 25.49% |
186 | 131/514 |
187 | 36.65% |
188 | 847/2311 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
199 |
200 |
201 |
208 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chimee-plugin-controlbar
2 |
3 | ## install
4 |
5 | 安装
6 |
7 | ```shell
8 | # 依赖于 chimee, 首先需要安装 chimee
9 | npm install chimee
10 | # 安装控制条组件
11 | npm install chimee-plugin-controlbar
12 | ```
13 |
14 | 使用
15 |
16 | ```javascript
17 | import chimee from 'chimee';
18 | import chimeePluginControlbar from 'chimee-plugin-controlbar';
19 |
20 | // 安装插件
21 | chimee.install(chimeePluginControlbar);
22 | const player = new chimee({
23 | // ...
24 | // 使用插件
25 | plugin: [
26 | chimeePluginControlbar.name // 或者 'chimeeControl'
27 | ]
28 | });
29 | ```
30 |
31 | **也可以在页面中引用 `/lib/index.browser.js` 然后在页面中使用 chimeePluginControlbar**
32 |
33 | ## 配置
34 |
35 | 一个配置 🌰 更详细的配置例子, 可以[参考 /demo/index.html](https://github.com/Chimeejs/chimee-plugin-controlbar/blob/master/demo/index.html)
36 |
37 | ```javascript
38 | plugin: [{
39 | name: chimeePluginControlbar.name,
40 | majorColor: '',
41 | hoverColor: '',
42 | children: {
43 | volume: {
44 | icon: {
45 | low: '',
46 | high: ''
47 | },
48 | layout: 'vertical'
49 | }
50 | }
51 | }]
52 | ```
53 |
54 | ### 具体的参数配置
55 |
56 | #### name
57 | * 类型: string
58 | * 含义: 该插件名字, 在 chimee 中使用需要名字,需要唯一对应
59 | * 值: 'chimeeControl' | chimeePluginControlbar.name
60 | * 必需
61 |
62 | #### majorColor
63 | * 类型: string
64 | * 作用范围:
65 | * 该插件中,所有的 svg 图
66 | * 播放进度条,进度颜色
67 | * 声音控制条,音量颜色
68 | * 可选值: 十六进制颜色('#fff')
69 | * 默认值: '#de698c'
70 | * 非必需
71 |
72 | #### hoverColor
73 | * 类型: string
74 | * 作用范围:
75 | * 该插件中,所有的 svg 图
76 | * 可选值: 十六进制颜色('#fff')
77 | * 默认值: '#4c4c4c'
78 | * 非必需
79 | #### barShowByMouse
80 | * 类型: string
81 | * 作用:控制条显示由
82 | * move 触发 播放器的 mousemove 显示
83 | * enter/levae 鼠标进入/出,来控制 控制条显示/隐藏
84 | * 可选值: 'move', 'enter'
85 | * 默认值: 'move'
86 | * 非必需
87 | #### hideBarTime
88 | * 类型: number
89 | * 作用:hidebar 延迟时间
90 | * 默认值: 2000
91 | * 注意:barShowByMouse 为 move 时有效,enter 时为0, 用户设置无效
92 | * 非必需
93 | #### children
94 | * 类型: Object
95 | * 含义: 配置子组件是否展示/展示方式,还可以自己扩展子组件
96 | * 非必需
97 | * 目前支持的组件: play, progressTime, progressBar, volume, screen, clarity, playbackrate
98 |
99 | ##### 目前支持的组件及配置
100 |
101 | * play
102 | * 类型: Object
103 | * 含义: 配置播放暂停键 icon 及事件
104 | * 默认: {}
105 | * 可配置属性:
106 | * 生命周期:
107 | * create: 插入 dom 节点, 完成事件注册
108 | * destroy
109 | * bitmap: true/ false 是否是位图,默认 false, 如果用户采用位图的话,则把当前的默认 svg 都清空掉, 用户通过 css background 来自己设置图片
110 | * icon: play / pause 图标, 支持 svg 图
111 | * animate: 当前是一个 svg path 动画,可以传 path 来实现你想要的动画
112 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
113 | * 注意: icon animate bitmap 都是配置图的。 bitmap 优先。其次 icon ,最后取 animate 中的值
114 |
115 | 配置 🌰
116 |
117 | ```javascirpt
118 | {
119 | // 可以传两个 icon 来切换播放暂停状态
120 | icon: {
121 | play: '',
122 | pause: ''
123 | },
124 | // 当前是一个 svg path 动画,可以传 path 来实现你想要的动画
125 | animate: {
126 | path: {
127 | play: {
128 | left: ''
129 | },
130 | pause: {
131 | left: ''
132 | }
133 | }
134 | },
135 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
136 | event: {
137 | click () {
138 | console.log('');
139 | }
140 | }
141 | }
142 | ```
143 |
144 | * progressTime
145 | * 类型: Object
146 | * 含义: 时间展示组件,用来展示播放时间/开播时间/视频总时长
147 | * 默认: {}
148 | * 可配置属性:
149 | * 生命周期:
150 | * create: 插入 dom 节点, 完成事件注册
151 | * destroy
152 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
153 |
154 | 配置 🌰
155 |
156 | ```javascirpt
157 | {
158 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
159 | event: {
160 | click () {
161 | console.log('');
162 | }
163 | }
164 | }
165 | ```
166 |
167 | * progressBar
168 | * 类型: Object | false
169 | * 含义: 进度条控制组件
170 | * 默认: {}, 属性值为 false 的时候,表示,他是一个占位符,不现实,可以区分左右,目前只有 progressbar 有这个功能
171 | * 可配置属性:
172 | * 生命周期:
173 | * create: 插入 dom 节点, 完成事件注册
174 | * destroy
175 | * layout: 有两种位置, 一是,居中布局。二是,位于整个控制条顶部。
176 | * 可选值: 'top' / 'baseline'(默认)
177 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
178 |
179 | 配置 🌰
180 |
181 | ```javascirpt
182 | {
183 | layout: 'top',
184 |
185 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
186 | event: {
187 | click () {
188 | console.log('');
189 | }
190 | }
191 | }
192 | ```
193 |
194 | * volume
195 | * 类型: Object
196 | * 含义: 声音控制组件
197 | * 默认: {}
198 | * 可配置属性:
199 | * 生命周期:
200 | * create: 插入 dom 节点, 完成事件注册
201 | * destroy
202 | * layout: 有两种位置, 一是,垂直。二是,水平。
203 | * 可选值: 'vertical' / 'horizonal'(默认)
204 | * bitmap: true/ false 是否是位图,默认 false,如果用户采用位图的话,则把当前的默认 svg 都清空掉, 用户通过 css background 来自己设置图片
205 | * icon: 音量按钮的三个状态按钮,mute / low / high 最少写前两个
206 | * [暂时不支持]animate: 也可以配置,然后自己通过 css 来控制
207 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
208 | * 注意: icon bitmap 都是配置图的。 bitmap 优先。其次 icon
209 |
210 | 配置 🌰
211 |
212 | ```javascirpt
213 | volume: {
214 | icon: {
215 | low: ``,
216 | mute: ``,
217 | high: ``
218 | },
219 | layout: 'vertical',
220 |
221 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
222 | event: {
223 | click () {
224 | console.log('');
225 | }
226 | }
227 | },
228 | ```
229 |
230 | * screen
231 | * 类型: Object
232 | * 含义: 配置全屏/非全屏 icon 及事件
233 | * 默认: {}
234 | * 可配置属性:
235 | * 生命周期:
236 | * create: 插入 dom 节点, 完成事件注册
237 | * destroy
238 | * bitmap: true/ false 是否是位图,默认 false,如果用户采用位图的话,则把当前的默认 svg 都清空掉, 用户通过 css background 来自己设置图片
239 | * icon: full / small 图标, 支持 svg 图
240 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
241 | * 注意: icon bitmap 都是配置图的。 bitmap 优先。其次 icon
242 |
243 | 配置 🌰
244 |
245 | ```javascirpt
246 | {
247 | // 可以传两个 icon 来切换播放暂停状态
248 | icon: {
249 | full: '',
250 | small: ''
251 | },
252 |
253 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
254 | event: {
255 | click () {
256 | console.log('');
257 | }
258 | }
259 | }
260 | ```
261 |
262 | * clarity
263 | * 类型: Object
264 | * 含义: 切换清晰度组件
265 | * 默认: {}
266 | * 可配置参数
267 | * 生命周期:
268 | * create: 插入 dom 节点, 完成事件注册
269 | * destroy
270 | * list: []
271 | * duration
272 | * 类型:`number`
273 | * 默认:3
274 | * 单次视频加载的时长
275 | * 若在规定的时间段内加载不成功,则放弃此次任务。
276 | * 单位为秒,对应于主视频的播放时间,也就是说若主视频暂停播放,则时间停滞,但加载仍继续。
277 | * bias
278 | * 类型:`number`
279 | * 默认:0
280 | * 偏差区间,单位为秒
281 | * 若该值小于等于0,则在主视频播放到或超过约定时间点直接切换,若新视频加载失败,则放弃此次切换。
282 | * 若该值大于0,则当主视频播放到约定时间偏差区间里,一旦视频加载成功就切换。若超出偏差空间,则放弃此次切换。
283 | * repeatTimes
284 | * 类型:`number`
285 | * 默认:0
286 | * 重复次数
287 | * 若加载视频失败,则自动重新加载,直至重复次数耗尽。默认不重复加载。
288 | * increment
289 | * 类型:`number`
290 | * 默认:0
291 | * 每次重复时递增的时间,单位为秒
292 | * 一般而言加载失败都是因为超时加载失败,故每次重复的时候应相应延长加载时间。每次重复加载都会相应叠加该值对应的时间。
293 | * immediate
294 | * 类型:`boolean`
295 | * 默认:`false`
296 | * 新视频加载成功后是否立即切换无需等待到约定时间。
297 | * 注意空数组时不展示
298 |
299 | 配置 🌰
300 |
301 | ```javascirpt
302 | {
303 | list: [
304 | {name: '标清', src:''},
305 | {name: '高清', src: ''},
306 | {name: '原画', src: ''}
307 | ],
308 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
309 | event: {
310 | click () {
311 | console.log('');
312 | }
313 | }
314 | }
315 | ```
316 |
317 | 直播切流测试: http://chimee.org/demo/live-clarity.html
318 |
319 | 点播切流测试: http://chimee.org/demo/clarity.html
320 |
321 | * playbackrate
322 | * 类型: Object
323 | * 含义: 切换播放倍速组件
324 | * 默认: {}
325 | * 可配置参数
326 | * 生命周期:
327 | * create: 插入 dom 节点, 完成事件注册
328 | * destroy
329 | * list: []
330 | * defualt: 默认播放速率 boolean值
331 | * 注意空数组时不展示
332 |
333 | 配置 🌰
334 |
335 | ```javascirpt
336 | {
337 | // default 通过设置 default 来标明当前播放速率
338 | list: [
339 | {name: '0.5倍速', value: 0.5},
340 | {name: '1倍速', value: 1, default: true},
341 | {name: '2倍速', value: 2}
342 | ],
343 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
344 | event: {
345 | click () {
346 | console.log('');
347 | }
348 | }
349 | }
350 | ```
351 |
352 | * 自定义组件
353 | * 类型: Object
354 | * 含义: 自定义组件
355 | * 可配置属性:
356 | * 生命周期:
357 | * create: 插入 dom 节点, 完成事件注册
358 | * destroy
359 | * tag: 自定义标签名
360 | * html: 自定义标签中的 html 内容
361 | * event: 绑定 dom 事件, this 指向这个插件, 通过 this.$dom 可以拿到 dom 节点
362 | * 注意: css 写在自己项目中就好了
363 |
364 | 配置 🌰
365 |
366 | ```javascirpt
367 | {
368 | tag: '',
369 | html: ``,
370 | // 可以指定 event 来绑定一些事件,默认 this 是该插件,而不是 dom
371 | event: {
372 | click () {
373 | console.log('');
374 | }
375 | }
376 | }
377 | ```
378 |
379 | ##### 组件相关问题
380 |
381 | * Q: 子组件的默认顺序是什么?
382 |
383 | A: 在 children 没有配置的情况下会采用下面的顺序
384 |
385 | * 注意:根据 chimee 的参数 isLive 来判断是否是直播还是点播
386 |
387 | * 直播: play, progressTime, volume, screen
388 |
389 | * 点播: play, progressTime, progressTime, volume, screen
390 |
391 | * 如果用户配置了, 则按照用户的配置走,不论是否是直播还是点播
392 |
393 | * Q: 我可以控制顺序吗?
394 |
395 | A: 在 children 对象中,属性的书写顺序就是渲染顺序
396 |
397 | * 注意, progressbar 可以作为一个占位符存在
398 |
399 | * Q: 为什么我配置了一个组件后,其他默认组件就都不存在了?
400 |
401 | A: 假如 children 配置后, 会读 children 的属性,并渲染, 不会补充其他组件,所以,需要你把所有的组件都写.
402 |
403 | ## 方法
404 |
405 | * updateClarity
406 | * 含义: 更新清晰度列表
407 | * 参数:Array 清晰度列表
408 |
409 | 配置 🌰
410 |
411 | ```javascirpt
412 | function updateClarity() {
413 | player.load('http://yunxianchang.live.ujne7.com/vod-system-bj/103_368b70a5d4f-c5cc-42ff-b442-004168fc86a3.mp4');
414 | player.on('load', function () { // 在 load 事件后
415 | player.$plugins.chimeeControl.updateClarity([
416 | {name: '标清', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_368b70a5d4f-c5cc-42ff-b442-004168fc86a3.mp4'},
417 | {name: '高清', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_369ed890f51-1c38-42a7-9ce2-828492660c60.mp4'},
418 | {name: '超清', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_370cc2f40bd-a39f-472a-884f-f44fcd9c5ae0.mp4'},
419 | {name: '原画', src: 'http://yunxianchang.live.ujne7.com/vod-system-bj/103_371ab0c0fda-143d-4755-8727-d3cd12dce02d.mp4'}
420 | ]);
421 | });
422 | }
423 | ```
424 |
425 |
426 | ## 最后
427 |
428 | 欢迎各位大佬使用。有什么问题/建议,随时提。
429 |
--------------------------------------------------------------------------------
/coverage/chrome/prettify.js:
--------------------------------------------------------------------------------
1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
28 |
29 |
30 |
430 |