first commit

This commit is contained in:
编码猿
2024-09-27 01:25:59 +08:00
commit 546fffb620
78 changed files with 30293 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
备注/
dist/editor.mp4
dist/mm.mp4
.idea/
.DS_Store
dist/.DS_Store
src/assets/.DS_Store

1
.pydio Normal file
View File

@@ -0,0 +1 @@
d0965c03-cd36-4c61-9de0-09771211baa8

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

208
README.md Normal file
View File

@@ -0,0 +1,208 @@
## 1AVideo.js【开发版本..】
![AVideo](http://static.geekhelp.cn/AVideo.png)
`AVideo.js` 是一款支持高度自定义,使用起来十分简洁易懂的视频播放器,可以广泛的使用在各个主流的`PC`设备上,移动端还没开始做兼容性处理,请耐心等候!`AVideo.js`源码全部采用 `Typescript` 进行编写,严格规范代码。播放器的页面样式使用模板引擎 `Pug` 进行开发,后面会基于`Pug`编写更多的页面模板,用户在使用的时候可以任意选择不同的模板或者自定义等!`CSS`使用`Less`进行编写,最后源码使用`WebPack`进行打包构建!
`AVideo.js` 不依赖`JQuery`,采用 `Typescript` 然后打包成`Js`,干净而又纯净,您可以在自己的项目中随意和第三方进行组合使用,对项目无任何侵入和干扰!
`AVideo.js` 是个人兴趣爱好开发的,所以源码更新进度可能不是那么的快,但基本做到每天实现三四个功能!有任何问题可以提 `Issues`
## 2具备的功能
已完成功能:
- [x] 单视频地址和多视频源(清晰度)播放
- [x] 内置多套播放器模板布局
- [x] 视频标题显示
- [x] 自动播放视频
- [x] 设置默认音量
- [x] 设置预览图
- [x] 视频待播放列表
- [x] 视频镜像功能
- [x] 视频旋转功能
- [x] 视频循环播放功能
- [x] 视频画中画功能
- [x] 视频截图功能
- [x] 视频播放完成的回调
- [x] 视频开始播放的回调
- [x] 视频被暂停的回调
- [x] 视频播放出错的回调
- [x] 视频播放时间更新的回调
- [x] HLS视频播放功能
近期开发中功能:
- [ ] 视频自定义倍速功能
- [ ] 视频下载功能
- [ ] 键盘快捷键实现视频进度,音量控制
- [ ] 视频记忆播放功能
排期中的功能:
- [ ] 视频开始播放广告植入功能
- [ ] 视频打点功能
- [ ] 视频vip试看功能
## 3使用
1在你的网页中引入`AVideo.min.js``AVideo.min.css`
> [AVideo.min.js 下载地址](https://gitee.com/bmycode/AVideo/blob/master/dist/AVideo.min.js)
>
> [AVideo.min.css 下载地址](https://gitee.com/bmycode/AVideo/blob/master/dist/AVideo.min.css)
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AVideo.min.js</title>
<!--开发阶段使用的字体图标后面正式阶段会合并到AVideo.min.css正式版不需要特意引入-->
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1980004_lnqny5kta9g.css">
<link href="AVideo.min.css" rel="stylesheet">
</head>
<body>
<script src="AVideo.min.js"></script>
</body>
</html>
```
2`body`中放置一个想要存放播放器的标签即可:
```html
<div id="app"></div>
```
3`Js`初始化插件即可:
```js
new AVideo({
el: '#app', // 传入父标签
url: 'http://video.geekhelp.cn/vue33', // 视频播放地址
title: '测试传入视频播放标题',
width: '60%', // 播放器高度
height: '600px', // 播放器宽度
autoplay: false, // 是否开启自动播放
muted: false, // 是否静音 true 静音false 不静音
// 需要什么控件就把对应的值设为 true不需要就false或者不填key
controls: {
speed: true, // 倍速播放
rotate: true, // 需要视频旋转按钮
image: true, // 需要视频镜像按钮
play: true, // 需要视频播放按钮
loop: true, // 需要视频循环播放按钮
PictureInPicture: true, // 需要视频的画中画功能
ScreenShot: true, // 是否需要截图按钮
},
finish: (video) => {
console.log("视频播放完成:", video)
},
// 播放列表
playList: [
{ title: '传入视频播放列表测试1', url: '' },
{ title: '传入视频播放列表测试2', url: '' },
{ title: '传入视频播放列表测试3', url: '' },
{ title: '传入视频播放列表测试4', url: '' },
{ title: '传入视频播放列表测试5', url: '' },
],
// 预览图地址
poster: 'http://5b0988e595225.cdn.sohucs.com/images/20190705/52ea928741394f959974ec4e34860bcf.jpeg'
});
```
4完整页面模板
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1980004_lnqny5kta9g.css">
<style>
.vip_join {
color: #61bd12;
}
</style>
<link href="AVideo.min.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="AVideo.min.js"></script>
</body>
<script>
new AVideo({
el: '#app',
url: 'http://video.geekhelp.cn/vue33',
title: '测试传入视频播放标题',
width: '60%', // 播放器高度
height: '600px', // 播放器宽度
autoplay: false, // 是否开启自动播放
muted: false, // 是否静音 true 静音false 不静音
controls: {
speed: true, // 倍速播放
rotate: true, // 需要视频旋转按钮
image: true, // 需要视频镜像按钮
play: true, // 需要视频播放按钮
loop: true, // 需要视频循环播放按钮
PictureInPicture: true, // 需要视频的画中画功能
ScreenShot: true, // 是否需要截图按钮
},
finish: (video) => {
console.log("视频播放完成:", video)
},
// 播放列表
playList: [
{ title: '传入视频播放列表测试1', url: '' },
{ title: '传入视频播放列表测试2', url: '' },
{ title: '传入视频播放列表测试3', url: '' },
{ title: '传入视频播放列表测试4', url: '' },
{ title: '传入视频播放列表测试5', url: '' },
],
// 预览图地址
poster: 'http://5b0988e595225.cdn.sohucs.com/images/20190705/52ea928741394f959974ec4e34860bcf.jpeg'
});
</script>
</html>
```
## 4参数说明
**你没看错!!!详细的参数文档还没时间来编写,一个人忙不过来!后面会慢慢补上文档....**
## 5编译使用
因为你使用的`AVideo.min.js``AVideo.min.css`可能不是新版本,所以你可以下载源码自己编译打包新版本的源码然后使用!!
**下载:**
```bash
git clone https://gitee.com/bmycode/AVideo.git
```
**初始化:**
```bash
npm install
```
**打包 `AVideo.min.js`和`AVideo.min.css`**
```bash
npm run build
```

1
dist/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
872f3b43-8348-47ea-a954-d7c3f09c7a21

BIN
dist/1.mp4 vendored Normal file

Binary file not shown.

BIN
dist/2.mp4 vendored Normal file

Binary file not shown.

BIN
dist/3.mp4 vendored Normal file

Binary file not shown.

BIN
dist/4.mp4 vendored Normal file

Binary file not shown.

BIN
dist/5.mp4 vendored Normal file

Binary file not shown.

BIN
dist/6.mp4 vendored Normal file

Binary file not shown.

BIN
dist/7.mp4 vendored Normal file

Binary file not shown.

BIN
dist/8.mp4 vendored Normal file

Binary file not shown.

BIN
dist/9.mp4 vendored Normal file

Binary file not shown.

171
dist/AVideo.min.css vendored Normal file
View File

@@ -0,0 +1,171 @@
body,
ul,
li {
margin: 0;
padding: 0;
list-style: none;
}
#app,
html,
body {
width: 100%;
height: 100%;
}
a {
text-decoration: none;
}
.active {
color: #61bd12 !important;
}
.high {
position: absolute;
width: 50px;
top: -96px;
text-align: center;
right: -15px;
height: 98px;
display: none;
}
.a_controls i,
.a_top_menu i {
color: #fff;
font-size: 18px;
margin: 0 8px;
cursor: pointer;
position: relative;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
/*清除系统默认样式*/
height: 12px;
/*拖动块高度*/
width: 12px;
/*拖动块宽度*/
background: #fff;
/*拖动块背景*/
border-radius: 50%;
/*外观设置为圆形*/
border: solid 1px #ddd;
/*设置边框*/
}
.a_left_controls span {
color: #fff;
margin-left: 4px;
font-size: 14px;
}
.a_left_controls,
.a_right_controls {
padding: 0 15px;
}
.a_left_controls i:first-child,
.a_right_controls i:last-child {
margin: 0 !important;
}
.avideo {
position: relative;
width: 60%;
height: 600px;
background: #000;
overflow: hidden;
}
.avideo .a_video {
width: 100%;
height: 100%;
position: absolute;
transform-origin: center;
}
.avideo .a_side_menu {
width: 250px;
position: absolute;
height: calc(100% - 37px - 45px);
right: 0;
top: 37px;
background: #202020;
overflow-y: scroll;
}
.avideo .a_side_menu .list_item {
display: block;
width: 100%;
height: 40px;
color: #909090;
line-height: 40px;
padding-left: 10px;
cursor: pointer;
border-bottom: 1px solid #303030;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
}
.avideo .a_side_menu .list_item:hover {
color: #61bd12;
}
.avideo .a_top_menu {
position: absolute;
width: 100%;
left: 0;
top: 0;
height: 37px;
background: rgba(75, 75, 75, 0.15);
z-index: 3;
}
.avideo .a_top_menu .a_left_menu {
float: left;
display: flex;
height: 100%;
color: #fff;
align-items: center;
padding-left: 15px;
}
.avideo .a_top_menu .a_right_menu {
float: right;
display: flex;
align-items: center;
height: 100%;
}
.avideo .a_top_menu .a_right_menu i {
width: 25px;
height: 25px;
line-height: 25px;
background: #000;
border-radius: 100%;
text-align: center;
}
.avideo .a_controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 45px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 3;
}
.avideo .a_controls .a_progress {
-webkit-appearance: none;
position: absolute;
top: -4px;
outline: none;
width: 100% !important;
background: -webkit-linear-gradient(#61bd12, #61bd12) no-repeat, #ffffff78;
background-size: 0% 100%;
/*设置左右宽度比例*/
height: 3px;
/*横条的高度*/
}
.avideo .a_controls .a_vip {
position: absolute;
color: #fff;
top: -60px;
padding: 5px 12px;
font-size: 14px;
left: 10px;
background-color: #333;
border-radius: 15px;
}
.avideo .a_controls .icon-suduspeed8 {
font-size: 16px !important;
}

357
dist/AVideo.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
dist/App.js vendored Normal file
View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
var init_1 = require("./core/init");
require("./assets/less/index.less");
// @ts-ignore
window.AVideo = init_1.Init;

1
dist/core/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
da927b6d-303c-4057-9486-2f534c77d640

1
dist/core/annotation/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
ec7ea387-e5ec-4847-8993-3681672646eb

18
dist/core/annotation/Dom.annotation.js vendored Normal file
View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Dom = void 0;
var OptionParams_model_1 = require("../model/OptionParams.model");
var ClassName_config_1 = require("../config/ClassName.config");
function Dom() {
return function (target, propertyName) {
setTimeout(function () {
try {
target[propertyName] = OptionParams_model_1.default.VideoDom.querySelector("." + ClassName_config_1.ClassName[propertyName]);
}
catch (e) {
throw new Error("\u9519\u8BEF\u4FE1\u606F\uFF1A\u9700\u8981\u7ED1\u5B9A\u4E8B\u4EF6\u7684\u7C7B\u540D\uFF1A" + ClassName_config_1.ClassName[propertyName] + "\u4E0D\u5B58\u5728\n 1\uFF1A\u8BF7\u68C0\u67E5\u7C7B\u540D\u662F\u5426\u5B58\u5728\n 2\uFF1A\u5220\u9664 ui/*.ui.ts \u4EE3\u7801\u4E2D\u5BF9\u5E94\u7684 Dom() \u88C5\u9970\u5668 \u548C \u65B9\u6CD5\n ");
}
}, 300);
};
}
exports.Dom = Dom;

41
dist/core/annotation/Ioc.annotation.js vendored Normal file
View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Inject = exports.Injectable = void 0;
var Ioc_model_1 = require("../model/Ioc.model");
/**
* 收集类依赖
* @constructor
*/
function Injectable() {
return function (_constructor) {
if (Ioc_model_1.default.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
}
else {
//注册
Ioc_model_1.default.classPool = [_constructor];
}
};
}
exports.Injectable = Injectable;
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
function Inject() {
return function (target, propertyName) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
var propertyType = Reflect.getMetadata('design:type', target, propertyName);
if (Ioc_model_1.default.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
}
else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (Ioc_model_1.default.classPool[Ioc_model_1.default.classPool.indexOf(propertyType)])();
}
};
}
exports.Inject = Inject;

1
dist/core/config/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
cf028485-f491-4537-bc8b-ccecb6619660

24
dist/core/config/ClassName.config.js vendored Normal file
View File

@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClassName = void 0;
var ClassName;
(function (ClassName) {
// 播放按钮的div class 名称
ClassName["Play"] = "icon-bofang";
ClassName["Video"] = "a_video";
ClassName["Pause"] = "icon-tingzhi1";
ClassName["Loop"] = "icon-xunhuan";
ClassName["Stop"] = "icon-tingzhi1";
ClassName["Active"] = "active";
ClassName["High"] = "icon-huaban-";
ClassName["PictureInPicture"] = "icon-ai223";
ClassName["Rotate"] = "icon-xuanzhuan1";
ClassName["Image"] = "icon-jingxiang2";
ClassName["ScreenShot"] = "icon-jietu_huaban";
ClassName["VideoProgress"] = "a_progress";
ClassName["VideoTime"] = "a_videotime";
ClassName["Speed"] = "icon-suduspeed8";
ClassName["FullScreen"] = "icon-quanping_huaban";
ClassName["playList"] = "icon-liebiao";
ClassName["SideMenu"] = "a_side_menu";
})(ClassName = exports.ClassName || (exports.ClassName = {}));

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VideoMimeType = void 0;
var VideoMimeType;
(function (VideoMimeType) {
VideoMimeType["HLS"] = "application/x-mpegurl";
VideoMimeType["TS"] = "video/MP2T";
VideoMimeType["MP4"] = "video/mp4";
VideoMimeType["STREA"] = "application/octet-stream";
VideoMimeType["FLV"] = "video/x-flv";
VideoMimeType["MOV"] = "video/quicktime";
VideoMimeType["AVI"] = "video/x-msvideo";
})(VideoMimeType = exports.VideoMimeType || (exports.VideoMimeType = {}));

64
dist/core/config/index.config.js vendored Normal file
View File

@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Config = void 0;
var Config = /** @class */ (function () {
function Config() {
}
Config.DefalutOptionParams = {
el: '#app',
url: 'http://demo-videos.qnsdk.com/movies/qiniu.mp4',
title: '欢迎使用 AVideo 播放器!',
layout: 'DefaultLayout',
width: '100%',
height: '400px',
autoplay: false,
muted: false,
volume: 1,
poster: '',
playList: [{ title: '1', url: '222' }],
controls: {
image: true,
setting: true,
rotate: true,
play: true,
loop: true,
speed: true,
FullScreen: true,
down: true,
PictureInPicture: true,
memory: true,
ScreenShot: true,
theme: true,
progressMarkers: [],
vip: {},
},
paused: function (video) { },
play: function (video) { },
finish: function (video) { },
error: function (video) { },
timeupdate: function (video) { },
};
Config.IconConfig = {
topIcon: [
{ title: '下载', icon: 'icon-down1', key: 'down' },
{ title: '列表', icon: 'icon-liebiao', key: 'playList' },
{ title: '设置', icon: 'icon-setting', key: 'setting' },
],
leftIcon: [
{ title: '播放', icon: 'icon-bofang', key: 'play' },
{ title: '循环播放', icon: 'icon-xunhuan', key: 'loop' },
],
rightIcon: [
{ title: '皮肤', icon: 'icon-jingzi', key: 'theme' },
{ title: '高清', icon: 'icon-huaban-', key: 'high' },
{ title: '旋转', icon: 'icon-xuanzhuan1', key: 'rotate' },
{ title: '镜像', icon: 'icon-jingxiang2', key: 'image' },
{ title: '截图', icon: 'icon-jietu_huaban', key: 'ScreenShot' },
{ title: '倍速', icon: 'icon-suduspeed8', key: 'speed' },
{ title: '画中画', icon: 'icon-ai223', key: 'PictureInPicture' },
{ title: '全屏', icon: 'icon-quanping_huaban', key: 'FullScreen' },
]
};
return Config;
}());
exports.Config = Config;

49
dist/core/init.js vendored Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Init = void 0;
var OptionParams_model_1 = require("./model/OptionParams.model");
var index_config_1 = require("./config/index.config");
var index_ui_1 = require("./ui/index.ui");
var Ioc_annotation_1 = require("./annotation/Ioc.annotation");
/**
* 核心必传参数的校验
*/
var Init = /** @class */ (function () {
function Init(OptionParams) {
this.OptionParams = OptionParams;
this.checkParams();
}
Init.prototype.checkParams = function () {
if (this.OptionParams.hasOwnProperty("el") && this.OptionParams.hasOwnProperty("url")) {
if (this.OptionParams.controls && Object.keys(this.OptionParams.controls).length == 0) {
throw new Error("请勿传入空controls的配置");
}
this.OptionParams.playList === undefined
? delete index_config_1.Config.DefalutOptionParams.playList
: null;
// 将用户传入的参数和默认参数进行合并,如果值冲突,优先采用用户的配置来覆盖默认值
Object.assign(index_config_1.Config.DefalutOptionParams, this.OptionParams);
// 保存配置到存取器,供全局使用
OptionParams_model_1.default.OptionParams = index_config_1.Config.DefalutOptionParams;
this.ApplicationUI.GenerateDom();
}
else {
throw new Error("请检查必传参数el 和 url是否传入");
}
};
__decorate([
Ioc_annotation_1.Inject(),
__metadata("design:type", index_ui_1.ApplicationUI)
], Init.prototype, "ApplicationUI", void 0);
return Init;
}());
exports.Init = Init;

1
dist/core/model/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
47c4d976-d8f6-4ae2-8240-a45a7c685ece

26
dist/core/model/Ioc.model.js vendored Normal file
View File

@@ -0,0 +1,26 @@
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var IocModel = /** @class */ (function () {
function IocModel() {
this._classPool = [];
}
Object.defineProperty(IocModel.prototype, "classPool", {
get: function () {
return this._classPool;
},
set: function (value) {
this._classPool = __spreadArrays(value, this._classPool);
},
enumerable: false,
configurable: true
});
return IocModel;
}());
exports.default = new IocModel();

28
dist/core/model/OptionParams.model.js vendored Normal file
View File

@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var OptionParamsModel = /** @class */ (function () {
function OptionParamsModel() {
}
Object.defineProperty(OptionParamsModel.prototype, "VideoDom", {
get: function () {
return this._VideoDom;
},
set: function (value) {
this._VideoDom = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(OptionParamsModel.prototype, "OptionParams", {
get: function () {
return this._OptionParams;
},
set: function (value) {
this._OptionParams = value;
},
enumerable: false,
configurable: true
});
return OptionParamsModel;
}());
exports.default = new OptionParamsModel();

1
dist/core/types/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
b1d18617-233a-490c-a87f-28dd4b26d70e

2
dist/core/types/OptionParams.types.js vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

1
dist/core/ui/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
900aa138-dace-4080-ac1c-261135bfcb71

307
dist/core/ui/controls.ui.js vendored Normal file
View File

@@ -0,0 +1,307 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlsUi = void 0;
var OptionParams_model_1 = require("../model/OptionParams.model");
var index_utils_1 = require("../utils/index.utils");
var Ioc_annotation_1 = require("../annotation/Ioc.annotation");
var Dom_annotation_1 = require("../annotation/Dom.annotation");
var ClassName_config_1 = require("../config/ClassName.config");
/**
* 底部控件条的基础功能设置
*/
var ControlsUi = /** @class */ (function () {
function ControlsUi() {
}
/**
* 播放按钮被点击,播放视频
* @constructor
*/
ControlsUi.prototype.BindEventPlay = function () {
var _this = this;
this.Play.onclick = function () { return __awaiter(_this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!this.Video.paused) return [3 /*break*/, 2];
return [4 /*yield*/, this.Video.play()];
case 1:
_a = _b.sent();
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, this.Video.pause()];
case 3:
_a = _b.sent();
_b.label = 4;
case 4:
_a;
this.Play.classList.toggle(ClassName_config_1.ClassName.Stop);
return [2 /*return*/];
}
});
}); };
};
ControlsUi.prototype.BindEventPlayList = function () {
var _this = this;
var SideMenu = this.Utils.$("." + ClassName_config_1.ClassName.SideMenu);
this.playList.onclick = function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (SideMenu.style.right == "0px") {
this.Utils.$("." + ClassName_config_1.ClassName.SideMenu).style.right = '-250px';
}
else {
this.Utils.$("." + ClassName_config_1.ClassName.SideMenu).style.right = '0';
}
return [2 /*return*/];
});
}); };
};
/**
* 视频全屏
* @constructor
*/
ControlsUi.prototype.BindEventFullScreen = function () {
var _this = this;
this.FullScreen.onclick = function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!document.fullscreenElement) return [3 /*break*/, 2];
this.Video.parentElement.style.width = '100%';
this.Video.parentElement.style.height = '100%';
return [4 /*yield*/, document.documentElement.requestFullscreen()];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
if (!document.exitFullscreen) return [3 /*break*/, 4];
this.Video.parentElement.style.width = OptionParams_model_1.default.OptionParams.width;
this.Video.parentElement.style.height = OptionParams_model_1.default.OptionParams.height;
return [4 /*yield*/, document.exitFullscreen()];
case 3:
_a.sent();
_a.label = 4;
case 4:
this.FullScreen.classList.toggle(ClassName_config_1.ClassName.Active);
return [2 /*return*/];
}
});
}); };
};
/**
* 截图按钮
* @constructor
*/
ControlsUi.prototype.BindEventScreenShot = function () {
var _this = this;
var scale = 0.65;
this.ScreenShot.onclick = function () {
var canvas = document.createElement("canvas");
canvas.width = _this.Video.videoWidth * scale;
canvas.height = _this.Video.videoHeight * scale;
canvas.getContext('2d')
.drawImage(_this.Video, 0, 0, canvas.width, canvas.height);
_this.Utils.downLoadFile(canvas.toDataURL('image/jpeg', 1.0), 'image/jpeg');
};
};
/**
* 循环播放的开关
* @constructor
*/
ControlsUi.prototype.BindEventLoop = function () {
var _this = this;
this.Loop.onclick = function () {
_this.Video.loop ? _this.Video.loop = false : _this.Video.loop = true;
_this.Loop.classList.toggle(ClassName_config_1.ClassName.Active);
};
};
/**
* 视频镜像功能
* @constructor
*/
ControlsUi.prototype.BindEventImage = function () {
var _this = this;
// 延迟解决 ondblclick 时候会触发 onclick 的问题
var ImageStatus = true, timeId = null; // true rotateY 镜像false rotateX 镜像
// 双击视频恢复
this.Image.ondblclick = function () {
clearTimeout(timeId);
ImageStatus = true;
_this.Video.style.cssText = "\n transform: rotateY(0deg); \n ";
};
// 单击镜像
this.Image.onclick = function () {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (ImageStatus) {
ImageStatus = false;
_this.Video.style.cssText = "\n transform: rotateY(180deg); \n ";
}
else {
ImageStatus = true;
_this.Video.style.cssText = "\n transform: rotateX(180deg); \n ";
}
}, 250);
};
};
/**
* TODO 视频倍速的功能 实现中...
* @constructor
*/
ControlsUi.prototype.BindEventSpeed = function () {
// 默认视频倍速
var defaultSpeed = [0.5, 1.0, 1.25, 1.5, 2.0];
this.Speed.onclick = function () {
// 如果传入的是数组
if (Array.isArray(OptionParams_model_1.default.OptionParams.controls.speed)) {
defaultSpeed = OptionParams_model_1.default.OptionParams.controls.speed;
console.log("如果传入的是数组");
// 如果传入的是true表示开启默认的倍速播放功能
}
else if (OptionParams_model_1.default.OptionParams.controls.speed == true) {
console.log("表示开启默认的倍速播放功能");
}
};
};
/**
* 画中画
* @constructor
*/
ControlsUi.prototype.BindEventPictureInPicture = function () {
var _this = this;
this.PictureInPicture.onclick = function () {
if (document.pictureInPictureElement == null) {
_this.Video.requestPictureInPicture();
_this.PictureInPicture.classList.add(ClassName_config_1.ClassName.Active);
}
else {
document.exitPictureInPicture();
_this.PictureInPicture.classList.remove(ClassName_config_1.ClassName.Active);
}
};
/**
* 监听退出画中画功能
*/
this.Video.addEventListener('leavepictureinpicture', function () {
_this.PictureInPicture.classList.remove(ClassName_config_1.ClassName.Active);
_this.Play.classList.toggle(ClassName_config_1.ClassName.Stop);
});
};
/**
* 视频旋转
* @constructor
*/
ControlsUi.prototype.BindEventRotate = function () {
var _this = this;
var currentRotate = 0, RotateNumber = 0;
this.Rotate.onclick = function () {
if (RotateNumber >= 4) {
currentRotate = 0;
RotateNumber = 0;
}
currentRotate += OptionParams_model_1.default.OptionParams.controls.rotate == true
? 90
: OptionParams_model_1.default.OptionParams.controls.rotate;
RotateNumber++;
if (currentRotate == 180 || currentRotate == 360 ||
currentRotate == -180 || currentRotate == -360) {
_this.Video.style.cssText = "\n width: 100%;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%) rotate(" + currentRotate + "deg);\n ";
return false;
}
else {
// 视频旋转 90°270°后视频的宽度 等于 父元素的高度
_this.Video.style.cssText = "\n width: " + OptionParams_model_1.default.OptionParams.height + ";\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%) rotate(" + currentRotate + "deg);\n ";
}
};
};
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], ControlsUi.prototype, "Play", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", Object)
], ControlsUi.prototype, "Video", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], ControlsUi.prototype, "Loop", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], ControlsUi.prototype, "PictureInPicture", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], ControlsUi.prototype, "Rotate", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLLIElement)
], ControlsUi.prototype, "Image", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLLIElement)
], ControlsUi.prototype, "ScreenShot", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLLIElement)
], ControlsUi.prototype, "Speed", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLLIElement)
], ControlsUi.prototype, "FullScreen", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLLIElement)
], ControlsUi.prototype, "playList", void 0);
__decorate([
Ioc_annotation_1.Inject(),
__metadata("design:type", index_utils_1.Utils)
], ControlsUi.prototype, "Utils", void 0);
return ControlsUi;
}());
exports.ControlsUi = ControlsUi;

191
dist/core/ui/coreSettings.ui.js vendored Normal file
View File

@@ -0,0 +1,191 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreSettingsUi = void 0;
var Dom_annotation_1 = require("../annotation/Dom.annotation");
var OptionParams_model_1 = require("../model/OptionParams.model");
var ClassName_config_1 = require("../config/ClassName.config");
var index_utils_1 = require("../utils/index.utils");
var Ioc_annotation_1 = require("../annotation/Ioc.annotation");
/**
* 视频核心功能设置
*/
var CoreSettingsUi = /** @class */ (function () {
function CoreSettingsUi() {
}
/**
* 设置视频预览图地址
* @constructor
*/
CoreSettingsUi.prototype.BindEventVideoPoster = function () {
this.Video.poster = OptionParams_model_1.default.OptionParams.poster;
};
/**
* 渲染视频播放列表
* @constructor
*/
CoreSettingsUi.prototype.BindEventPlayVideoList = function () {
var _this = this;
var SideMenu = document.querySelectorAll("." + ClassName_config_1.ClassName.SideMenu + " .list_item");
SideMenu.forEach(function (v, index) {
v.onclick = function () {
_this.VideoProgress.style.backgroundSize = "0% 100%";
setTimeout(function () { return _this.VideoProgress.value = String(0); }, 1);
_this.VideoProgress.step = '0.000000000000000001';
_this.Video.src = OptionParams_model_1.default.OptionParams.playList[index].url;
_this.Play.classList.remove(ClassName_config_1.ClassName.Stop);
_this.Play.classList.add(ClassName_config_1.ClassName.Play);
};
});
};
/**
* 设置视频宽高
* @constructor
*/
CoreSettingsUi.prototype.BindEventVideoSize = function () {
this.Video.parentElement.style.width = OptionParams_model_1.default.OptionParams.width;
this.Video.parentElement.style.height = OptionParams_model_1.default.OptionParams.height;
};
/**
* 设置视频的自动播放 和 是否静音
* @constructor
*/
CoreSettingsUi.prototype.BindEventVideoAutoPlayAndMuted = function () {
this.Video.autoplay = OptionParams_model_1.default.OptionParams.autoplay;
this.Video.muted = OptionParams_model_1.default.OptionParams.muted;
// 谷歌等众多浏览器禁止video标签有声的自动播放所以这里如果想实现有声而且自动播放视频那么需要特殊处理
// 前端用户需要开启 autoplay true 自动播放muted true 静音
if (OptionParams_model_1.default.OptionParams.autoplay && OptionParams_model_1.default.OptionParams.muted) {
console.log("无声,自动播放");
this.Play.classList.toggle(ClassName_config_1.ClassName.Stop);
}
};
/**
* 配置单个视频地址 & 多个清晰度视频地址
* @constructor
*/
CoreSettingsUi.prototype.BindEventEndedVideoSetUrl = function () {
return __awaiter(this, void 0, void 0, function () {
var UL_1;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(typeof OptionParams_model_1.default.OptionParams.url === "string")) return [3 /*break*/, 2];
return [4 /*yield*/, this.Utils.CheckVideoType(OptionParams_model_1.default.OptionParams.url)
// 多清晰度视频的数组
];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
// 数组默认第一条视频作为播放视频
return [4 /*yield*/, this.Utils.CheckVideoType(OptionParams_model_1.default.OptionParams.url[0].url)];
case 3:
// 数组默认第一条视频作为播放视频
_a.sent();
UL_1 = document.createElement("ul");
UL_1.className = "high";
OptionParams_model_1.default.OptionParams.url.map(function (val, index) {
var li = document.createElement("li");
li.textContent = val.title;
// 默认第一个显示激活样式
index == 0 ? li.classList.toggle(ClassName_config_1.ClassName.Active) : null;
li.onclick = function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// 清除所有li激活类名
li.parentElement.childNodes.forEach(function (s) { return s.className = ""; });
// 为点击的里高亮激活
li.className = ClassName_config_1.ClassName.Active;
// 切换视频地址
return [4 /*yield*/, this.Utils.CheckVideoType(val.url)];
case 1:
// 切换视频地址
_a.sent();
return [2 /*return*/];
}
});
}); };
// 填充li到ul中
UL_1.appendChild(li);
});
// 鼠标悬浮 显示清晰度选择菜单
this.High.onmouseover = function () { return UL_1.style.display = "block"; };
this.High.onmouseout = function () { return UL_1.style.display = "none"; };
// 填充ul到清晰度选择的图标中
this.High.appendChild(UL_1);
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], CoreSettingsUi.prototype, "Play", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLVideoElement)
], CoreSettingsUi.prototype, "Video", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], CoreSettingsUi.prototype, "High", void 0);
__decorate([
Ioc_annotation_1.Inject(),
__metadata("design:type", index_utils_1.Utils)
], CoreSettingsUi.prototype, "Utils", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLInputElement)
], CoreSettingsUi.prototype, "VideoProgress", void 0);
return CoreSettingsUi;
}());
exports.CoreSettingsUi = CoreSettingsUi;

114
dist/core/ui/eventListeners.ui.js vendored Normal file
View File

@@ -0,0 +1,114 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventListenersUi = void 0;
var Dom_annotation_1 = require("../annotation/Dom.annotation");
var OptionParams_model_1 = require("../model/OptionParams.model");
var ClassName_config_1 = require("../config/ClassName.config");
var Ioc_annotation_1 = require("../annotation/Ioc.annotation");
var index_utils_1 = require("../utils/index.utils");
/**
* 视频播放的各种事件监听和处理
*/
var EventListenersUi = /** @class */ (function () {
function EventListenersUi() {
}
/**
* 监听 视频数据长度发生改变的时候去获取视频的总时间,
* 并设置到页面上显示出来
* @constructor
*/
EventListenersUi.prototype.BindEventDurationchange = function () {
var _this = this;
this.Video.addEventListener('durationchange', function () {
_this.setVideoTime(_this.Video.duration);
});
};
// 播放按钮被点击,播放视频
EventListenersUi.prototype.BindEventEnded = function () {
var _this = this;
this.Video.addEventListener('ended', function () {
// 视频播放完成后,将暂停按钮样式改为播放状态
_this.Play.classList.toggle(ClassName_config_1.ClassName.Stop);
// 回调用户的事件
OptionParams_model_1.default.OptionParams.finish(_this.Video);
});
};
/**
* 视频播放过程中时间发生改变设置进度条进度
* @constructor
*/
EventListenersUi.prototype.BindEventVideoTimeUpdate = function () {
var _this = this;
this.Video.ontimeupdate = function () {
// 进度条前进值 = 当前时间 / 总视频时间 * 100
var w = _this.Video.currentTime / _this.Video.duration * 100;
// 设置进度条已播放过的颜色
_this.VideoProgress.style.backgroundSize = w + "% 100%";
// 设置进度条方块前进值
_this.VideoProgress.value = String(w);
// 进度条步进值
_this.VideoProgress.step = '0.000000000000000001';
_this.setVideoTime();
};
};
/**
* 进度条被拖动的时候
* @constructor
*/
EventListenersUi.prototype.BindEventVideoOnProgress = function () {
var _this = this;
this.VideoProgress.oninput = function () {
// 拿到被拖动滑块的滑动距离
var offsetX = _this.VideoProgress.value;
// 滑动后对应的视频播放时间 = 滑动距离 / 总距离 * 时间总时间
_this.Video.currentTime = offsetX / 100 * _this.Video.duration;
// 设置进度条已播放过的颜色
_this.VideoProgress.style.backgroundSize = offsetX + "% 100%";
// 拖动的时候自动播放了,所以把播放按钮改成暂停按钮
_this.Play.classList.add(ClassName_config_1.ClassName.Stop);
_this.setVideoTime();
};
};
/**
* 设置视频的 当前播放时间 / 视频总时间
*/
EventListenersUi.prototype.setVideoTime = function (time) {
if (time === undefined) {
this.VideoTime.innerText = this.Utils.formatTime(this.Video.currentTime) + "/" + this.Utils.formatTime(this.Video.duration);
}
else {
this.VideoTime.innerText = this.Utils.formatTime(this.Video.currentTime) + "/" + this.Utils.formatTime(time);
}
};
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], EventListenersUi.prototype, "Play", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLMediaElement)
], EventListenersUi.prototype, "Video", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLInputElement)
], EventListenersUi.prototype, "VideoProgress", void 0);
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLElement)
], EventListenersUi.prototype, "VideoTime", void 0);
__decorate([
Ioc_annotation_1.Inject(),
__metadata("design:type", index_utils_1.Utils)
], EventListenersUi.prototype, "Utils", void 0);
return EventListenersUi;
}());
exports.EventListenersUi = EventListenersUi;

49
dist/core/ui/index.ui.js vendored Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApplicationUI = void 0;
/// <reference path = "../../types/index.d.ts" />
var OptionParams_model_1 = require("../model/OptionParams.model");
var index_utils_1 = require("../utils/index.utils");
var Ioc_annotation_1 = require("../annotation/Ioc.annotation");
var controls_ui_1 = require("./controls.ui");
var eventListeners_ui_1 = require("./eventListeners.ui");
var coreSettings_ui_1 = require("./coreSettings.ui");
/**
* 生成播放器的Dom树绑定各种事件处理
*/
var ApplicationUI = /** @class */ (function () {
function ApplicationUI() {
}
ApplicationUI.prototype.GenerateDom = function () {
// 获取用户传入的最外层父节点
this.el = this.Utils.$(OptionParams_model_1.default.OptionParams.el);
// 保存视频播放器的DOM树后面需要操作DOM树为各个按钮绑定事件处理
OptionParams_model_1.default.VideoDom = this.Utils.ParseDom(this.Utils.getLayout());
// 自动调用传入类中方法名包含 'BindEventxxx' 的方法
// 实现各种点击事件的自动绑定
this.Utils.callMethods([
coreSettings_ui_1.CoreSettingsUi, controls_ui_1.ControlsUi, eventListeners_ui_1.EventListenersUi
]);
// 将模板pug和参数配置渲染后得到html string转换为dom树
// 然后将已经绑定好各种事件的dom注入到用户传入的 div 中给用户使用!!
this.el.appendChild(OptionParams_model_1.default.VideoDom);
};
__decorate([
Ioc_annotation_1.Inject(),
__metadata("design:type", index_utils_1.Utils)
], ApplicationUI.prototype, "Utils", void 0);
ApplicationUI = __decorate([
Ioc_annotation_1.Injectable()
], ApplicationUI);
return ApplicationUI;
}());
exports.ApplicationUI = ApplicationUI;

1
dist/core/utils/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
3aa64d68-ed8b-496e-8cb2-6ad35dab223c

206
dist/core/utils/index.utils.js vendored Normal file
View File

@@ -0,0 +1,206 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Utils = void 0;
var OptionParams_model_1 = require("../model/OptionParams.model");
var index_config_1 = require("../config/index.config");
var Ioc_annotation_1 = require("../annotation/Ioc.annotation");
var Hls = require("hls.js");
var VideoMimeType_config_1 = require("../config/VideoMimeType.config");
var Dom_annotation_1 = require("../annotation/Dom.annotation");
var Utils = /** @class */ (function () {
function Utils() {
}
Utils.prototype.$ = function (el) {
return document.querySelector(el);
};
/**
* 自动调用类中的事件绑定方法
* @param params 类数组
*/
Utils.prototype.callMethods = function (params) {
setTimeout(function () {
return params.forEach(function (val) {
return Object.getOwnPropertyNames(val.prototype).splice(1).filter(function (f) {
return f.includes("BindEvent");
}).forEach(function (v) {
return (new val())[v]();
});
});
}, 400);
};
/**
* 修改元素类名
* @param dom
* @param className
*/
Utils.prototype.addClassName = function (dom, className) {
dom.classList.toggle(className);
};
/**
* 视同用户填入的模板传入配置渲染输出HTML
*/
Utils.prototype.getLayout = function () {
var render = require("../../assets/tpl/" + OptionParams_model_1.default.OptionParams.layout + ".pug");
return render({ params: Object.assign(OptionParams_model_1.default.OptionParams, index_config_1.Config.IconConfig) });
};
/**
* 将 string 转换为 dom 元素
* @param arg
* @constructor
*/
Utils.prototype.ParseDom = function (arg) {
var objE = document.createElement("div");
objE.innerHTML = arg;
return objE.childNodes[0];
};
Utils.prototype.Render = function () {
// // 获取dom树中最后一个child也就是 script
// let oldScript = father.lastChild;
// // 从dom树中删除 script 代码因为不管是innerHTML还是appendChild dom中的script都不执行
// // 所以需要特殊处理
// father.removeChild(father.childNodes[father.childNodes.length-1])
// // 将去除 script 后的dom树插入到网页中
// el.appendChild(father);
//
// // 生成 script写入 js插入 到网页中这时候js可以执行
// let newScript = document.createElement('script');
// newScript.type = 'text/javascript';
// newScript.innerHTML = (oldScript as HTMLElement).innerHTML;
// el.appendChild(newScript);
};
/**
* 使用fetch 获取视频地址的编码类型,
* 然后采用不同的播放方式
* @constructor
*/
Utils.prototype.CheckVideoType = function (URL) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch("http://localhost:9000/check/url?url=" + URL)
.then(function (res) {
return res.json();
})
.then(function (res) {
switch (res.result) {
// HLS 播放支持
case VideoMimeType_config_1.VideoMimeType.HLS:
_this.PlayHls(_this.Video, URL);
break;
// MP4播放支持
case VideoMimeType_config_1.VideoMimeType.MP4:
_this.Video.src = URL;
break;
// MP4不同编码的播放支持
case VideoMimeType_config_1.VideoMimeType.STREA:
_this.Video.src = URL;
break;
default:
throw new Error("不支持播放的视频地址");
}
})];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* 下载文件
* @param href 文件地址
* @param fileType 文件类型
*/
Utils.prototype.downLoadFile = function (href, fileType) {
if (fileType === void 0) { fileType = 'image/png'; }
var dlLink = document.createElement('a');
dlLink.download = "AVideo_" + (new Date()).valueOf().toString();
dlLink.href = href;
dlLink.dataset.downloadurl = [fileType, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
};
/**
* 格式化时间
* @param t
*/
Utils.prototype.formatTime = function (t) {
if (isNaN(t)) {
return "00:00:00";
}
var h = parseInt(String(t / 3600));
h = h < 10 ? '0' + h : h;
var m = parseInt(String(t % 3600 / 60));
m = m < 10 ? '0' + m : m;
var s = parseInt(String(t % 60));
s = s < 10 ? '0' + s : s;
return h + ':' + m + ':' + s;
};
/**
* 使用插件 hls.js 播放视频
* @param Video
* @param url
* @constructor
*/
Utils.prototype.PlayHls = function (Video, url) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(Video);
};
__decorate([
Dom_annotation_1.Dom(),
__metadata("design:type", HTMLVideoElement)
], Utils.prototype, "Video", void 0);
Utils = __decorate([
Ioc_annotation_1.Injectable()
], Utils);
return Utils;
}());
exports.Utils = Utils;

21489
dist/hls.js vendored Normal file

File diff suppressed because it is too large Load Diff

67
dist/index.html vendored Normal file
View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1980004_lnqny5kta9g.css">
<style>
.vip_join {
color: #61bd12;
}
</style>
<link href="AVideo.min.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="AVideo.min.js"></script>
</body>
<script>
// 阿里云视频 https://player.alicdn.com/video/editor.mp4
// 七牛云视频 http://demo-videos.qnsdk.com/movies/qiniu.mp4
// m3u8 地址 http://video.geekhelp.cn/vue33
new AVideo({
el: '#app',
// url: 'http://video.geekhelp.cn/vue33',
url: [
{ title: '高清', url: '1.mp4' },
{ title: '标清', url: '2.mp4' },
{ title: '低清', url: '3.mp4' },
],
title: '测试传入视频播放标题',
width: '60%', // 播放器高度
height: '600px', // 播放器宽度
autoplay: false, // 是否开启自动播放
muted: false, // 是否静音 true 静音false 不静音
// 需要什么控件就把对应的值设为 true不需要就false或者不填key
controls: {
speed: true, // 倍速播放
rotate: true, // 需要视频旋转按钮
image: true, // 需要视频镜像按钮
play: true, // 需要视频播放按钮
loop: true, // 需要视频循环播放按钮
PictureInPicture: true, // 需要视频的画中画功能
ScreenShot: true, // 是否需要截图按钮
},
finish: (video) => {
console.log("视频播放完成:", video)
},
// 播放列表
playList: [
{ title: '传入视频播放列表测试1', url: '1.mp4' },
{ title: '传入视频播放列表测试2', url: '2.mp4' },
{ title: '传入视频播放列表测试3', url: '3.mp4' },
{ title: '传入视频播放列表测试4', url: '4.mp4' },
{ title: '传入视频播放列表测试5', url: '5.mp4' },
],
// 预览图地址
poster: 'http://5b0988e595225.cdn.sohucs.com/images/20190705/52ea928741394f959974ec4e34860bcf.jpeg'
});
</script>
</html>

35
index.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
class Test {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
var test = new Test("张三");
console.log("test: ",test);
console.log(test.getName());
// 类的 prototype 等于 对象的 __proto__
// 显式原型 = 隐式原型
// console.log(Test.prototype === test.__proto__);
// constructor 外的方法会成为实例化对象的共有方法
// 类的公有方法不会开辟新的内存,以此达到节省内存的目的
</script>
</html>

5248
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "demos",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack --config webpack.config.js --mode=development",
"ts": "tsc -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/hls.js": "^0.13.0",
"css-loader": "^4.2.0",
"html-webpack-plugin": "^4.5.0",
"less": "^3.12.2",
"less-loader": "^6.2.0",
"mini-css-extract-plugin": "^0.9.0",
"pug": "^3.0.0",
"pug-loader": "^2.4.0",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"style-loader": "^1.2.1",
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-split-by-path": "^2.0.0"
},
"dependencies": {
"hls.js": "^0.14.7",
"request": "^2.88.2"
}
}

1
src/.pydio Normal file
View File

@@ -0,0 +1 @@
b05d4506-4c6e-4129-a129-fa5dbe999daa

6
src/App.ts Normal file
View File

@@ -0,0 +1,6 @@
import 'reflect-metadata';
import { Init } from "./core/init";
import "./assets/less/index.less";
// @ts-ignore
window.AVideo = Init;

1
src/assets/.pydio Normal file
View File

@@ -0,0 +1 @@
fa05898b-4b32-4898-9acc-1bf90c6cd7a4

1
src/assets/less/.pydio Normal file
View File

@@ -0,0 +1 @@
d91f23e4-786c-4d2b-b376-d964d609a2ac

View File

@@ -0,0 +1,14 @@
body,ul,li {
margin: 0;
padding: 0;
list-style: none;
}
#app, html, body {
width: 100%;
height: 100%;
}
a {
text-decoration: none;
}

View File

@@ -0,0 +1,2 @@
@color: red;
@ActiveColor: #61bd12;

161
src/assets/less/index.less Normal file
View File

@@ -0,0 +1,161 @@
@import "clear.less";
@import "color.less";
.active {
color: @ActiveColor !important;
}
.high {
position: absolute;
width: 50px;
top: -96px;
text-align: center;
right: -15px;
height: 98px;
display: none;
}
//公共样式
.a_controls i,
.a_top_menu i{
color: #fff;
font-size: 18px;
margin: 0 8px;
cursor: pointer;
position: relative;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;/*清除系统默认样式*/
height:12px;/*拖动块高度*/
width: 12px;/*拖动块宽度*/
background: #fff;/*拖动块背景*/
border-radius: 50%; /*外观设置为圆形*/
border: solid 1px #ddd; /*设置边框*/
}
.a_left_controls {
span {
color: #fff;
margin-left: 4px;
font-size: 14px;
}
}
.a_left_controls,
.a_right_controls {
padding: 0 15px;
}
.a_left_controls i:first-child,
.a_right_controls i:last-child {
margin: 0 !important;
}
.avideo {
position: relative;
width: 60%;
height: 600px;
background: #000;
overflow: hidden;
.a_video {
width: 100%;
height: 100%;
position: absolute;
transform-origin: center;
}
.a_side_menu {
width: 250px;
padding-top: 37px;
position: absolute;
height: 100%;
right: -250px;
top: 0;
background: #202020;
overflow-y: scroll;
padding-bottom: 50px;
box-sizing: border-box;
transition: all 0.3s cubic-bezier(0.51, 0.29, 0.54, 1.32);
.list_item {
display: block;
width: 100%;
height: 40px;
color: #909090;
line-height: 40px;
padding-left: 10px;
cursor: pointer;
border-bottom: 1px solid #303030;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
&:hover{
color: #61bd12;
}
}
}
.a_top_menu {
position: absolute;
width: 100%;
left: 0;
top: 0;
height: 37px;
background: rgba(75,75,75,.15);
z-index: 3;
.a_left_menu {
float: left;
display: flex;
height: 100%;
color: #fff;
align-items: center;
padding-left: 15px;
}
.a_right_menu {
float: right;
display: flex;
align-items: center;
height: 100%;
i {
width: 25px;
height: 25px;
line-height: 25px;
background: #000;
border-radius: 100%;
text-align: center;
}
}
}
.a_controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 45px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 3;
.a_progress {
-webkit-appearance: none;
position: absolute;
top: -4px;
outline: none;
width: 100% !important;
background: -webkit-linear-gradient(#61bd12, #61bd12) no-repeat, #ffffff78;
background-size: 0% 100%;/*设置左右宽度比例*/
height: 3px;/*横条的高度*/
}
.a_vip {
position: absolute;
color: #fff;
top: -60px;
padding: 5px 12px;
font-size: 14px;
left: 10px;
background-color: #333;
border-radius: 15px;
}
.icon-suduspeed8 {
font-size: 16px !important;
}
}
}

1
src/assets/tpl/.pydio Normal file
View File

@@ -0,0 +1 @@
1b2ef0d8-5fed-4d3e-84cb-530ff48ac354

View File

@@ -0,0 +1,45 @@
.avideo
video.a_video
// 侧边播放列表
if params.playList !== undefined
.a_side_menu
each val in params.playList
div.list_item #{val.title}
// 顶部菜单
.a_top_menu
.a_left_menu #{params.title}
.a_right_menu
each val in params.topIcon
if params.controls[val.key]
i(class=`iconfont ${val.icon}` title=`${val.title}`)
else if val.key == 'playList' && params.playList !== undefined
i.iconfont.icon-liebiao(title='列表')
// 底部进度条和菜单
.a_controls
// 进度条
input.a_progress(type='range', value='0')
// 试看提示
.a_vip
| 试看60秒
a.vip_join(href='') 加入会员
| 观看完整视频。
// 左边播放等按钮
.a_left_controls
each val in params.leftIcon
if params.controls[val.key]
i(class=`iconfont ${val.icon}` title=`${val.title}`)
span.a_videotime 00:00:00/00:00:00
// 遍历按钮,根据用户配置决定是否显示
.a_right_controls
each val in params.rightIcon
if params.controls[val.key] == true
i(class=`iconfont ${val.icon}` title=`${val.title}`)
else if (val.key == "high" && Array.isArray(params.url) )
i(class=`iconfont ${val.icon}` title=`${val.title}`)
else if (val.key == "rotate" && typeof params.controls[val.key] == "number")
i(class=`iconfont ${val.icon}` title=`${val.title}`)

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
1df3ad8a-5c58-442f-93c0-3e72b9fd3577

View File

@@ -0,0 +1 @@
966472fa-a3be-4729-a71b-ae988035f1e7

View File

@@ -0,0 +1,18 @@
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
export function Dom() {
return function (target: any, propertyName: string) {
setTimeout(()=> {
try {
target[propertyName] = OptionParamsModel.VideoDom.querySelector(`.${ClassName[propertyName]}`)
} catch (e) {
throw new Error(`错误信息:需要绑定事件的类名:${ClassName[propertyName]}不存在
1请检查类名是否存在
2删除 ui/*.ui.ts 代码中对应的 Dom() 装饰器 和 方法
`)
}
}
,300)
}
}

View File

@@ -0,0 +1,36 @@
import IocModel from "../model/Ioc.model";
/**
* 收集类依赖
* @constructor
*/
export function Injectable() {
return (_constructor: { new (...args: any[]): {}; }) => {
if(IocModel.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
} else {
//注册
IocModel.classPool = [_constructor]
}
}
}
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
export function Inject() {
return function (target: any, propertyName: string) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
const propertyType: any = Reflect.getMetadata('design:type', target, propertyName);
if (IocModel.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
} else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (IocModel.classPool[IocModel.classPool.indexOf(propertyType)])()
}
}
}

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
fbfd9291-1d66-4dcb-bcd8-abbe474d7c84

View File

@@ -0,0 +1,22 @@
import { Config } from "./index.config";
export enum ClassName {
// 播放按钮的div class 名称
Play = "icon-bofang",
Video = "a_video",
Pause = "icon-tingzhi1",
Loop = "icon-xunhuan",
Stop = "icon-tingzhi1",
Active = "active",
High = "icon-huaban-",
PictureInPicture = "icon-ai223",
Rotate = "icon-xuanzhuan1",
Image = "icon-jingxiang2",
ScreenShot = "icon-jietu_huaban",
VideoProgress = "a_progress",
VideoTime = "a_videotime",
Speed = "icon-suduspeed8",
FullScreen = "icon-quanping_huaban",
playList = "icon-liebiao",
SideMenu = "a_side_menu"
}

View File

@@ -0,0 +1,9 @@
export enum VideoMimeType {
HLS = "application/x-mpegurl",
TS = "video/MP2T",
MP4 = "video/mp4",
STREA = "application/octet-stream",
FLV = 'video/x-flv',
MOV = 'video/quicktime',
AVI = 'video/x-msvideo',
}

View File

@@ -0,0 +1,61 @@
import { OptionParamsTypes } from "../types/OptionParams.types";
export class Config {
public static DefalutOptionParams: OptionParamsTypes = {
el: '#app', // 父节点
url: 'http://demo-videos.qnsdk.com/movies/qiniu.mp4', // 视频地址参数2[{title:'高清', url: ''},{title: '普清', url: ''}]
title: '欢迎使用 AVideo 播放器!',
layout: 'DefaultLayout',// 选择的播放器布局主题
width: '100%', // 高度
height: '400px', // 宽度
autoplay: false, // 是否自动播放
muted: false, // 是否静音 true 静音false 不静音
volume: 1, // 初始音量 0 ~ 1 之间
poster: '',// 视频预览图地址
playList: [{title:'1',url:'222'}], // 显示视频播放列表
controls: { // 配置视频底部 播放控件
image: true, // 是否需要镜像功能
setting: true, // 是否需要设置功能
rotate: true, // 是否开启旋转开启后视频旋转默认顺时针90°度数。参数还支持传入正负的度数例如-30将会逆时针使用用户传入度数进行旋转
play: true, // 是否需要播放按钮
loop: true, // 是否需要循环播放按钮
speed: true, // 是否需要倍速,如果传入数组,类似:[0.3, 0.5, 1, 1.5, 2, 3],那么采用用户传入
FullScreen: true, // 是否需要全屏按钮
down: true, // 是否需要下载按钮
PictureInPicture: true, // 是否需要画中画按钮
memory: true, // 是否开启记忆播放 参数2{ tips: '是否继续从2:00开始播放' } tips 自定义提示文字
ScreenShot: true, // 是否需要截图,
theme: true, // 是否显示皮肤切换的按钮
progressMarkers: [],
vip: {},
},
paused: (video)=> {}, // 视频被暂停的回调
play: (video)=> {}, // 视频开始播放的回调
finish: (video)=> {}, // 视频播放完成的回调
error: (video)=> {}, // 视频播放出错的回调
timeupdate: (video)=> {}, // 视频播放时间更新的回调
};
public static IconConfig = {
topIcon: [
{ title: '下载', icon: 'icon-down1', key: 'down' },
{ title: '列表', icon: 'icon-liebiao', key: 'playList' },
{ title: '设置', icon: 'icon-setting', key: 'setting' },
],
leftIcon: [
{ title: '播放', icon: 'icon-bofang', key: 'play' },
{ title: '循环播放', icon: 'icon-xunhuan', key: 'loop' },
],
rightIcon: [
{ title: '皮肤', icon: 'icon-jingzi', key: 'theme' },
{ title: '高清', icon: 'icon-huaban-', key: 'high' },
{ title: '旋转', icon: 'icon-xuanzhuan1', key: 'rotate' },
{ title: '镜像', icon: 'icon-jingxiang2', key: 'image' },
{ title: '截图', icon: 'icon-jietu_huaban', key: 'ScreenShot' },
{ title: '倍速', icon: 'icon-suduspeed8', key: 'speed' },
{ title: '画中画', icon: 'icon-ai223', key: 'PictureInPicture' },
{ title: '全屏', icon: 'icon-quanping_huaban', key: 'FullScreen' },
]
}
}

43
src/core/init.ts Normal file
View File

@@ -0,0 +1,43 @@
import { OptionParamsTypes } from "./types/OptionParams.types";
import OptionParamsModel from "./model/OptionParams.model";
import { Config } from "./config/index.config";
import { ApplicationUI } from "./ui/index.ui";
import { Inject, Injectable } from "./annotation/Ioc.annotation";
/**
* 核心必传参数的校验
*/
export class Init {
@Inject()
private ApplicationUI!: ApplicationUI;
private OptionParams: OptionParamsTypes;
constructor(OptionParams: OptionParamsTypes) {
this.OptionParams = OptionParams;
this.checkParams()
}
public checkParams(): void {
if (this.OptionParams.hasOwnProperty("el") && this.OptionParams.hasOwnProperty("url")) {
if (this.OptionParams.controls && Object.keys(this.OptionParams.controls).length == 0) {
throw new Error("请勿传入空controls的配置")
}
this.OptionParams.playList === undefined
? delete Config.DefalutOptionParams.playList
: null
// 将用户传入的参数和默认参数进行合并,如果值冲突,优先采用用户的配置来覆盖默认值
Object.assign(Config.DefalutOptionParams, this.OptionParams)
// 保存配置到存取器,供全局使用
OptionParamsModel.OptionParams = Config.DefalutOptionParams;
this.ApplicationUI.GenerateDom();
} else {
throw new Error("请检查必传参数el 和 url是否传入")
}
}
}

1
src/core/model/.pydio Normal file
View File

@@ -0,0 +1 @@
3564163c-013d-4d8c-94bc-a306dd223191

View File

@@ -0,0 +1,13 @@
class IocModel {
private _classPool: Array<{ new (...args: any[]): {}; }> = [];
get classPool(): Array<{ new(...args: any[]): {} }> {
return this._classPool;
}
set classPool(value: Array<{ new(...args: any[]): {} }>) {
this._classPool = [...value, ...this._classPool]
}
}
export default new IocModel();

View File

@@ -0,0 +1,24 @@
import { OptionParamsTypes } from "../types/OptionParams.types";
class OptionParamsModel {
private _OptionParams: OptionParamsTypes;
private _VideoDom: HTMLElement;
get VideoDom(): HTMLElement {
return this._VideoDom;
}
set VideoDom(value: HTMLElement) {
this._VideoDom = value;
}
get OptionParams(): OptionParamsTypes {
return this._OptionParams;
}
set OptionParams(value: OptionParamsTypes) {
this._OptionParams = value;
}
}
export default new OptionParamsModel();

1
src/core/types/.pydio Normal file
View File

@@ -0,0 +1 @@
df13c4ef-91b6-4544-a480-23a706749d0a

View File

@@ -0,0 +1,62 @@
// 播放地址
interface UrlItem {
title: string,
url: string
}
// 记忆打点配置
interface MemoryItem {
tips: string
}
// 视频打点 的配置数组
interface progressMarkersItem {
title: string,
theme?: number,
offset: number,
img?: string,
describe?: string
}
// 视频底部ui控件
export interface ControlsTypes {
setting?: boolean,
rotate?: boolean | number,
image?: boolean,
play?: boolean,
loop?: boolean,
speed?: boolean | number[],
FullScreen?: boolean,
down?: boolean,
PictureInPicture?: boolean,
memory?: boolean | MemoryItem,
ScreenShot?: boolean,
theme?: boolean,
progressMarkers: Array<progressMarkersItem>,
vip: {
time?: number,
tip?: string,
popup?: string
}
}
// 主要配置
export interface OptionParamsTypes {
el: string,
url: string | Array<UrlItem>,
title: string,
layout?: string,
width?: string,
height?: string,
autoplay?: boolean,
muted?: boolean,
volume?: number,
poster?: string,
playList?: Array<UrlItem>,
controls?: ControlsTypes,
paused?: (video: HTMLMediaElement) => any;
play?: (video: HTMLMediaElement) => any;
finish?: (video: HTMLMediaElement) => any;
error?: (video: HTMLMediaElement) => any;
timeupdate?: (video: HTMLMediaElement) => any;
}

1
src/core/ui/.pydio Normal file
View File

@@ -0,0 +1 @@
c86ae987-557c-4c45-ae05-8678ae14b9a5

239
src/core/ui/controls.ui.ts Normal file
View File

@@ -0,0 +1,239 @@
import OptionParamsModel from "../model/OptionParams.model";
import { Utils } from "../utils/index.utils";
import { Inject, Injectable } from "../annotation/Ioc.annotation";
import { Dom } from "../annotation/Dom.annotation";
import {ClassName} from "../config/ClassName.config";
/**
* 底部控件条的基础功能设置
*/
export class ControlsUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLMediaElement & HTMLVideoElement;
@Dom()
private readonly Loop!: HTMLElement;
@Dom()
private readonly PictureInPicture!: HTMLElement;
@Dom()
private readonly Rotate!: HTMLElement;
@Dom()
private readonly Image!: HTMLLIElement;
@Dom()
private readonly ScreenShot!: HTMLLIElement;
@Dom()
private readonly Speed!: HTMLLIElement;
@Dom()
private readonly FullScreen!: HTMLLIElement;
@Dom()
private readonly playList!: HTMLLIElement;
@Inject()
private readonly Utils!: Utils;
/**
* 播放按钮被点击,播放视频
* @constructor
*/
public BindEventPlay(): void {
this.Play.onclick = async () => {
this.Video.paused ? await this.Video.play() : await this.Video.pause()
this.Play.classList.toggle(ClassName.Stop)
}
}
public BindEventPlayList(): void {
let SideMenu = this.Utils.$(`.${ClassName.SideMenu}`);
this.playList.onclick = async () => {
if (SideMenu.style.right == "0px") {
this.Utils.$(`.${ClassName.SideMenu}`).style.right = '-250px'
} else {
this.Utils.$(`.${ClassName.SideMenu}`).style.right = '0'
}
}
}
/**
* 视频全屏
* @constructor
*/
public BindEventFullScreen(): void {
this.FullScreen.onclick = async () => {
if (!document.fullscreenElement) {
this.Video.parentElement.style.width = '100%'
this.Video.parentElement.style.height = '100%'
await document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
this.Video.parentElement.style.width = OptionParamsModel.OptionParams.width
this.Video.parentElement.style.height = OptionParamsModel.OptionParams.height
await document.exitFullscreen();
}
}
this.FullScreen.classList.toggle(ClassName.Active)
}
}
/**
* 截图按钮
* @constructor
*/
public BindEventScreenShot(): void {
let scale = 0.65;
this.ScreenShot.onclick = () => {
var canvas = document.createElement("canvas");
canvas.width = this.Video.videoWidth * scale;
canvas.height = this.Video.videoHeight * scale;
canvas.getContext('2d')
.drawImage(this.Video, 0, 0, canvas.width, canvas.height);
this.Utils.downLoadFile(canvas.toDataURL('image/jpeg',1.0), 'image/jpeg')
}
}
/**
* 循环播放的开关
* @constructor
*/
public BindEventLoop(): void {
this.Loop.onclick = () => {
this.Video.loop ? this.Video.loop = false : this.Video.loop = true
this.Loop.classList.toggle(ClassName.Active)
}
}
/**
* 视频镜像功能
* @constructor
*/
public BindEventImage(): void {
// 延迟解决 ondblclick 时候会触发 onclick 的问题
let ImageStatus: boolean = true, timeId = null; // true rotateY 镜像false rotateX 镜像
// 双击视频恢复
this.Image.ondblclick = () => {
clearTimeout(timeId);
ImageStatus = true
this.Video.style.cssText = `
transform: rotateY(0deg);
`
}
// 单击镜像
this.Image.onclick = () => {
clearTimeout(timeId);
timeId = setTimeout(() => {
if (ImageStatus) {
ImageStatus = false
this.Video.style.cssText = `
transform: rotateY(180deg);
`
} else {
ImageStatus = true
this.Video.style.cssText = `
transform: rotateX(180deg);
`
}
}, 250);
}
}
/**
* TODO 视频倍速的功能 实现中...
* @constructor
*/
public BindEventSpeed(): void {
// 默认视频倍速
let defaultSpeed: number[] = [0.5, 1.0, 1.25, 1.5, 2.0];
this.Speed.onclick = () => {
// 如果传入的是数组
if (Array.isArray(OptionParamsModel.OptionParams.controls.speed)) {
defaultSpeed = OptionParamsModel.OptionParams.controls.speed;
console.log("如果传入的是数组")
// 如果传入的是true表示开启默认的倍速播放功能
} else if (OptionParamsModel.OptionParams.controls.speed == true) {
console.log("表示开启默认的倍速播放功能")
}
}
}
/**
* 画中画
* @constructor
*/
public BindEventPictureInPicture(): void {
this.PictureInPicture.onclick = () => {
if (document.pictureInPictureElement == null) {
this.Video.requestPictureInPicture();
this.PictureInPicture.classList.add(ClassName.Active)
} else {
document.exitPictureInPicture();
this.PictureInPicture.classList.remove(ClassName.Active)
}
}
/**
* 监听退出画中画功能
*/
this.Video.addEventListener('leavepictureinpicture', () => {
this.PictureInPicture.classList.remove(ClassName.Active)
this.Play.classList.toggle(ClassName.Stop)
});
}
/**
* 视频旋转
* @constructor
*/
public BindEventRotate(): void {
let currentRotate: number = 0, RotateNumber: number = 0;
this.Rotate.onclick = () => {
if (RotateNumber >= 4) {
currentRotate = 0;
RotateNumber = 0;
}
currentRotate += <boolean>OptionParamsModel.OptionParams.controls.rotate == true
? 90
: <number>OptionParamsModel.OptionParams.controls.rotate;
RotateNumber++
if (currentRotate == 180 || currentRotate == 360 ||
currentRotate == -180 || currentRotate == -360) {
this.Video.style.cssText = `
width: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(${currentRotate}deg);
`;
return false
} else {
// 视频旋转 90°270°后视频的宽度 等于 父元素的高度
this.Video.style.cssText = `
width: ${OptionParamsModel.OptionParams.height};
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(${currentRotate}deg);
`;
}
}
}
}

View File

@@ -0,0 +1,120 @@
import {Dom} from "../annotation/Dom.annotation";
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
import { VideoMimeType } from "../config/VideoMimeType.config";
import {Utils} from "../utils/index.utils";
import {Inject} from "../annotation/Ioc.annotation";
import * as Hls from "hls.js";
/**
* 视频核心功能设置
*/
export class CoreSettingsUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLVideoElement;
@Dom()
private readonly High!: HTMLElement;
@Inject()
private readonly Utils: Utils;
@Dom()
private readonly VideoProgress!: HTMLInputElement;
/**
* 设置视频预览图地址
* @constructor
*/
public BindEventVideoPoster(): void {
this.Video.poster = OptionParamsModel.OptionParams.poster
}
/**
* 渲染视频播放列表
* @constructor
*/
public BindEventPlayVideoList(): void {
let SideMenu: NodeListOf<HTMLDivElement> = document.querySelectorAll(`.${ClassName.SideMenu} .list_item`)
SideMenu.forEach((v,index) => {
v.onclick = () => {
this.VideoProgress.style.backgroundSize = `0% 100%`;
setTimeout(() => this.VideoProgress.value = String(0),1)
this.VideoProgress.step = '0.000000000000000001'
this.Video.src = OptionParamsModel.OptionParams.playList[index].url;
this.Play.classList.remove(ClassName.Stop)
this.Play.classList.add(ClassName.Play)
}
})
}
/**
* 设置视频宽高
* @constructor
*/
public BindEventVideoSize(): void {
this.Video.parentElement.style.width = OptionParamsModel.OptionParams.width
this.Video.parentElement.style.height = OptionParamsModel.OptionParams.height
}
/**
* 设置视频的自动播放 和 是否静音
* @constructor
*/
public BindEventVideoAutoPlayAndMuted(): void {
this.Video.autoplay = OptionParamsModel.OptionParams.autoplay;
this.Video.muted = OptionParamsModel.OptionParams.muted;
// 谷歌等众多浏览器禁止video标签有声的自动播放所以这里如果想实现有声而且自动播放视频那么需要特殊处理
// 前端用户需要开启 autoplay true 自动播放muted true 静音
if (OptionParamsModel.OptionParams.autoplay && OptionParamsModel.OptionParams.muted) {
console.log("无声,自动播放")
this.Play.classList.toggle(ClassName.Stop)
}
}
/**
* 配置单个视频地址 & 多个清晰度视频地址
* @constructor
*/
public async BindEventEndedVideoSetUrl() {
// 单个视频地址
if (typeof OptionParamsModel.OptionParams.url === "string") {
await this.Utils.CheckVideoType(<string>OptionParamsModel.OptionParams.url)
// 多清晰度视频的数组
} else {
// 数组默认第一条视频作为播放视频
await this.Utils.CheckVideoType(OptionParamsModel.OptionParams.url[0].url)
let UL: HTMLUListElement = document.createElement("ul");
UL.className = "high";
OptionParamsModel.OptionParams.url.map((val,index) => {
let li: HTMLLIElement = document.createElement("li");
li.textContent = val.title;
// 默认第一个显示激活样式
index == 0 ? li.classList.toggle(ClassName.Active) : null
li.onclick = async () => {
// 清除所有li激活类名
li.parentElement.childNodes.forEach((s: HTMLLIElement)=> s.className = "")
// 为点击的里高亮激活
li.className = ClassName.Active
// 切换视频地址
await this.Utils.CheckVideoType(val.url)
};
// 填充li到ul中
UL.appendChild(li);
});
// 鼠标悬浮 显示清晰度选择菜单
this.High.onmouseover = () => UL.style.display = "block";
this.High.onmouseout = () => UL.style.display = "none";
// 填充ul到清晰度选择的图标中
this.High.appendChild(UL);
}
}
}

View File

@@ -0,0 +1,97 @@
import {Dom} from "../annotation/Dom.annotation";
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
import {Inject} from "../annotation/Ioc.annotation";
import { Utils } from "../utils/index.utils";
/**
* 视频播放的各种事件监听和处理
*/
export class EventListenersUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLMediaElement;
@Dom()
private readonly VideoProgress!: HTMLInputElement;
@Dom()
private readonly VideoTime!: HTMLElement;
@Inject()
private readonly Utils: Utils;
/**
* 监听 视频数据长度发生改变的时候去获取视频的总时间,
* 并设置到页面上显示出来
* @constructor
*/
public BindEventDurationchange(): void {
this.Video.addEventListener('durationchange', () => {
this.setVideoTime(this.Video.duration)
})
}
// 播放按钮被点击,播放视频
public BindEventEnded(): void {
this.Video.addEventListener('ended', () => {
// 视频播放完成后,将暂停按钮样式改为播放状态
this.Play.classList.toggle(ClassName.Stop)
// 回调用户的事件
OptionParamsModel.OptionParams.finish(this.Video)
})
}
/**
* 视频播放过程中时间发生改变设置进度条进度
* @constructor
*/
public BindEventVideoTimeUpdate() {
this.Video.ontimeupdate = () => {
// 进度条前进值 = 当前时间 / 总视频时间 * 100
let w = this.Video.currentTime / this.Video.duration * 100;
// 设置进度条已播放过的颜色
this.VideoProgress.style.backgroundSize = `${w}% 100%`;
// 设置进度条方块前进值
this.VideoProgress.value = String(w)
// 进度条步进值
this.VideoProgress.step = '0.000000000000000001'
this.setVideoTime()
}
}
/**
* 进度条被拖动的时候
* @constructor
*/
public BindEventVideoOnProgress(): void {
this.VideoProgress.oninput = () => {
// 拿到被拖动滑块的滑动距离
let offsetX = <any>this.VideoProgress.value;
// 滑动后对应的视频播放时间 = 滑动距离 / 总距离 * 时间总时间
this.Video.currentTime = offsetX / 100 * this.Video.duration;
// 设置进度条已播放过的颜色
this.VideoProgress.style.backgroundSize = `${offsetX}% 100%`;
// 拖动的时候自动播放了,所以把播放按钮改成暂停按钮
this.Play.classList.add(ClassName.Stop)
this.setVideoTime()
}
}
/**
* 设置视频的 当前播放时间 / 视频总时间
*/
public setVideoTime(time?: number): void {
if (time === undefined) {
this.VideoTime.innerText = `${this.Utils.formatTime(this.Video.currentTime)}/${this.Utils.formatTime(this.Video.duration)}`;
} else {
this.VideoTime.innerText = `${this.Utils.formatTime(this.Video.currentTime)}/${this.Utils.formatTime(time)}`;
}
}
}

38
src/core/ui/index.ui.ts Normal file
View File

@@ -0,0 +1,38 @@
/// <reference path = "../../types/index.d.ts" />
import OptionParamsModel from "../model/OptionParams.model";
import { Utils } from "../utils/index.utils";
import { Inject, Injectable } from "../annotation/Ioc.annotation";
import { ControlsUi } from "./controls.ui";
import { EventListenersUi } from "./eventListeners.ui";
import { CoreSettingsUi } from "./coreSettings.ui";
/**
* 生成播放器的Dom树绑定各种事件处理
*/
@Injectable()
export class ApplicationUI {
@Inject()
private Utils: Utils;
private el: HTMLElement
GenerateDom() {
// 获取用户传入的最外层父节点
this.el = this.Utils.$(OptionParamsModel.OptionParams.el);
// 保存视频播放器的DOM树后面需要操作DOM树为各个按钮绑定事件处理
OptionParamsModel.VideoDom = this.Utils.ParseDom(this.Utils.getLayout());
// 自动调用传入类中方法名包含 'BindEventxxx' 的方法
// 实现各种点击事件的自动绑定
this.Utils.callMethods([
CoreSettingsUi, ControlsUi, EventListenersUi
])
// 将模板pug和参数配置渲染后得到html string转换为dom树
// 然后将已经绑定好各种事件的dom注入到用户传入的 div 中给用户使用!!
this.el.appendChild(OptionParamsModel.VideoDom);
}
}

1
src/core/utils/.pydio Normal file
View File

@@ -0,0 +1 @@
b8fc9423-3e19-4e61-af70-8d7485eac391

View File

@@ -0,0 +1,152 @@
import OptionParamsModel from "../model/OptionParams.model";
import { Config } from "../config/index.config";
import { Injectable } from "../annotation/Ioc.annotation";
import * as Hls from "hls.js";
import {VideoMimeType} from "../config/VideoMimeType.config";
import {Dom} from "../annotation/Dom.annotation";
@Injectable()
export class Utils {
@Dom()
private readonly Video!: HTMLVideoElement;
public $(el: string): any {
return document.querySelector(el)
}
/**
* 自动调用类中的事件绑定方法
* @param params 类数组
*/
public callMethods(params: {new (...args: any[]): {}; }[] ): void {
setTimeout(() =>
params.forEach((val: { new (): any; } ) =>
Object.getOwnPropertyNames(val.prototype).splice(1).filter(f =>
f.includes("BindEvent")).forEach(v =>
(new val())[v]())),400)
}
/**
* 修改元素类名
* @param dom
* @param className
*/
addClassName<E extends HTMLElement>(dom: E, className: string): void {
dom.classList.toggle(className)
}
/**
* 视同用户填入的模板传入配置渲染输出HTML
*/
public getLayout(): string {
let render = require(`../../assets/tpl/${OptionParamsModel.OptionParams.layout}.pug`)
return render({ params: Object.assign(OptionParamsModel.OptionParams, Config.IconConfig) })
}
/**
* 将 string 转换为 dom 元素
* @param arg
* @constructor
*/
public ParseDom (arg: string): HTMLElement {
var objE = document.createElement("div");
objE.innerHTML = arg;
return objE.childNodes[0] as HTMLElement;
}
public Render() {
// // 获取dom树中最后一个child也就是 script
// let oldScript = father.lastChild;
// // 从dom树中删除 script 代码因为不管是innerHTML还是appendChild dom中的script都不执行
// // 所以需要特殊处理
// father.removeChild(father.childNodes[father.childNodes.length-1])
// // 将去除 script 后的dom树插入到网页中
// el.appendChild(father);
//
// // 生成 script写入 js插入 到网页中这时候js可以执行
// let newScript = document.createElement('script');
// newScript.type = 'text/javascript';
// newScript.innerHTML = (oldScript as HTMLElement).innerHTML;
// el.appendChild(newScript);
}
/**
* 使用fetch 获取视频地址的编码类型,
* 然后采用不同的播放方式
* @constructor
*/
public async CheckVideoType(URL: string): Promise<void> {
await fetch(`http://localhost:9000/check/url?url=${URL}`)
.then((res)=>{
return res.json()
})
.then((res)=>{
switch (res.result) {
// HLS 播放支持
case VideoMimeType.HLS:
this.PlayHls(this.Video, URL)
break;
// MP4播放支持
case VideoMimeType.MP4:
this.Video.src = URL;
break;
// MP4不同编码的播放支持
case VideoMimeType.STREA:
this.Video.src = URL;
break;
default:
throw new Error("不支持播放的视频地址")
}
});
}
/**
* 下载文件
* @param href 文件地址
* @param fileType 文件类型
*/
downLoadFile(href: string, fileType = 'image/png') {
var dlLink = document.createElement('a');
dlLink.download = `AVideo_${(new Date()).valueOf().toString()}`;
dlLink.href = href;
dlLink.dataset.downloadurl = [fileType, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
/**
* 格式化时间
* @param t
*/
formatTime(t: number){
if (isNaN(t)) {
return "00:00:00"
}
var h: string | number = parseInt(String(t / 3600))
h = h<10?'0'+h:h
var m: string | number = parseInt(String(t % 3600 / 60))
m = m<10?'0'+m:m
var s: string | number = parseInt(String(t % 60))
s = s<10?'0'+s:s
return h+':'+m+':'+s
}
/**
* 使用插件 hls.js 播放视频
* @param Video
* @param url
* @constructor
*/
public PlayHls(Video: HTMLVideoElement, url: string) {
let hls = new Hls();
hls.loadSource(url);
hls.attachMedia(Video);
}
}

56
src/index.pug Normal file
View File

@@ -0,0 +1,56 @@
doctype html
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
title
link(rel='stylesheet', href='http://at.alicdn.com/t/font_1980004_lnqny5kta9g.css')
style.
.vip_join {
color: #61bd12;
}
body
#app
script.
// 阿里云视频 https://player.alicdn.com/video/editor.mp4
// 七牛云视频 http://demo-videos.qnsdk.com/movies/qiniu.mp4
// m3u8 地址 http://video.geekhelp.cn/vue33
new AVideo({
el: '#app',
url: 'https://txmov2.a.yximgs.com/bs2/newWatermark/MzQ3ODMyOTQ1MzU_zh_4.mp4',
// url: [
// {title: '高清', url: 'http://video.geekhelp.cn/job_vue3_1.mp4'},
// {title: '标清', url: 'mm.mp4'},
// {title: '低清', url: 'http://video.geekhelp.cn/vue33'},
// ],
title: '测试传入视频播放标题',
width: '60%', // 播放器高度
height: '600px', // 播放器宽度
autoplay: false, // 是否开启自动播放
muted: false, // 是否静音 true 静音false 不静音
// 需要什么控件就把对应的值设为 true不需要就false或者不填key
controls: {
FullScreen: true, // 全屏
speed: true, // 倍速播放
rotate: true, // 需要视频旋转按钮
image: true, // 需要视频镜像按钮
play: true, // 需要视频播放按钮
loop: true, // 需要视频循环播放按钮
PictureInPicture: true, // 需要视频的画中画功能
ScreenShot: true, // 是否需要截图按钮
},
finish: (video)=> {
console.log("视频播放完成:", video)
},
// 播放列表
playList: [
{ title: '传入视频播放列表测试1', url: '1.mp4' },
{ title: '传入视频播放列表测试2', url: '2.mp4'},
{ title: '传入视频播放列表测试2', url: '3.mp4'},
{ title: '传入视频播放列表测试2', url: '4.mp4'},
{ title: '传入视频播放列表测试2', url: '5.mp4'},
{ title: '传入视频播放列表测试2', url: '6.mp4'},
{ title: '传入视频播放列表测试2', url: '7.mp4'},
{ title: '传入视频播放列表测试2', url: '8.mp4'},
{ title: '传入视频播放列表测试2', url: '9.mp4'},
],
});

1
src/types/.pydio Normal file
View File

@@ -0,0 +1 @@
f6e0c4c0-5991-474c-a342-e583e8a5ba1c

4
src/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare interface ParentNode {
querySelector(selectors: string): HTMLElement;
}
declare var window: Window & typeof globalThis;

18
tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"baseUrl": ".",
"rootDir": "src",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowJs": true,
"outDir": "./dist/"
},
"include": [
"./src/*"
],
"exclude": [
"node_modules"
]
}

72
webpack.config.js Normal file
View File

@@ -0,0 +1,72 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const request = require("request");
module.exports = {
entry: {
AVideo: './src/App.ts'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].min.js'
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
allowedHosts: [
'txmov2.a.yximgs.com'
],
before(app) {
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
next();
});
app.get('/check/url', async function(req, res) {
const options = { url: req.query.url, headers: { range: 'bytes=0-50' } };
request(options, async (error, response, body) => {
await res.json({ result: response.headers['content-type'] });
});
});
}
},
resolve: {
extensions: ['.ts', '.js', '.json']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: { loader: 'ts-loader' }
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPaht: '../'
}
},
'css-loader', 'less-loader'
]
},
{
test: /\.pug$/,
use: ["pug-loader"]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './src/index.pug')
}),
new MiniCssExtractPlugin({
filename: '[name].min.css'
})
]
};