first commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
备注/
|
||||
dist/editor.mp4
|
||||
dist/mm.mp4
|
||||
.idea/
|
||||
.DS_Store
|
||||
dist/.DS_Store
|
||||
src/assets/.DS_Store
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal 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
208
README.md
Normal file
@@ -0,0 +1,208 @@
|
||||
## 1:AVideo.js【开发版本..】
|
||||
|
||||

|
||||
|
||||
`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
1
dist/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
872f3b43-8348-47ea-a954-d7c3f09c7a21
|
||||
BIN
dist/1.mp4
vendored
Normal file
BIN
dist/1.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/2.mp4
vendored
Normal file
BIN
dist/2.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/3.mp4
vendored
Normal file
BIN
dist/3.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/4.mp4
vendored
Normal file
BIN
dist/4.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/5.mp4
vendored
Normal file
BIN
dist/5.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/6.mp4
vendored
Normal file
BIN
dist/6.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/7.mp4
vendored
Normal file
BIN
dist/7.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/8.mp4
vendored
Normal file
BIN
dist/8.mp4
vendored
Normal file
Binary file not shown.
BIN
dist/9.mp4
vendored
Normal file
BIN
dist/9.mp4
vendored
Normal file
Binary file not shown.
171
dist/AVideo.min.css
vendored
Normal file
171
dist/AVideo.min.css
vendored
Normal 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
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
7
dist/App.js
vendored
Normal 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
1
dist/core/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
da927b6d-303c-4057-9486-2f534c77d640
|
||||
1
dist/core/annotation/.pydio
vendored
Normal file
1
dist/core/annotation/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ec7ea387-e5ec-4847-8993-3681672646eb
|
||||
18
dist/core/annotation/Dom.annotation.js
vendored
Normal file
18
dist/core/annotation/Dom.annotation.js
vendored
Normal 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
41
dist/core/annotation/Ioc.annotation.js
vendored
Normal 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
1
dist/core/config/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cf028485-f491-4537-bc8b-ccecb6619660
|
||||
24
dist/core/config/ClassName.config.js
vendored
Normal file
24
dist/core/config/ClassName.config.js
vendored
Normal 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 = {}));
|
||||
13
dist/core/config/VideoMimeType.config.js
vendored
Normal file
13
dist/core/config/VideoMimeType.config.js
vendored
Normal 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
64
dist/core/config/index.config.js
vendored
Normal 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
49
dist/core/init.js
vendored
Normal 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
1
dist/core/model/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
47c4d976-d8f6-4ae2-8240-a45a7c685ece
|
||||
26
dist/core/model/Ioc.model.js
vendored
Normal file
26
dist/core/model/Ioc.model.js
vendored
Normal 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
28
dist/core/model/OptionParams.model.js
vendored
Normal 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
1
dist/core/types/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b1d18617-233a-490c-a87f-28dd4b26d70e
|
||||
2
dist/core/types/OptionParams.types.js
vendored
Normal file
2
dist/core/types/OptionParams.types.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
1
dist/core/ui/.pydio
vendored
Normal file
1
dist/core/ui/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
900aa138-dace-4080-ac1c-261135bfcb71
|
||||
307
dist/core/ui/controls.ui.js
vendored
Normal file
307
dist/core/ui/controls.ui.js
vendored
Normal 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
191
dist/core/ui/coreSettings.ui.js
vendored
Normal 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
114
dist/core/ui/eventListeners.ui.js
vendored
Normal 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
49
dist/core/ui/index.ui.js
vendored
Normal 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
1
dist/core/utils/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3aa64d68-ed8b-496e-8cb2-6ad35dab223c
|
||||
206
dist/core/utils/index.utils.js
vendored
Normal file
206
dist/core/utils/index.utils.js
vendored
Normal 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
21489
dist/hls.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
67
dist/index.html
vendored
Normal file
67
dist/index.html
vendored
Normal 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
35
index.html
Normal 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
5248
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal 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
1
src/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
b05d4506-4c6e-4129-a129-fa5dbe999daa
|
||||
6
src/App.ts
Normal file
6
src/App.ts
Normal 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
1
src/assets/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
fa05898b-4b32-4898-9acc-1bf90c6cd7a4
|
||||
1
src/assets/less/.pydio
Normal file
1
src/assets/less/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
d91f23e4-786c-4d2b-b376-d964d609a2ac
|
||||
14
src/assets/less/clear.less
Normal file
14
src/assets/less/clear.less
Normal 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;
|
||||
}
|
||||
2
src/assets/less/color.less
Normal file
2
src/assets/less/color.less
Normal file
@@ -0,0 +1,2 @@
|
||||
@color: red;
|
||||
@ActiveColor: #61bd12;
|
||||
161
src/assets/less/index.less
Normal file
161
src/assets/less/index.less
Normal 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
1
src/assets/tpl/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
1b2ef0d8-5fed-4d3e-84cb-530ff48ac354
|
||||
45
src/assets/tpl/DefaultLayout.pug
Normal file
45
src/assets/tpl/DefaultLayout.pug
Normal 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
1
src/core/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
1df3ad8a-5c58-442f-93c0-3e72b9fd3577
|
||||
1
src/core/annotation/.pydio
Normal file
1
src/core/annotation/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
966472fa-a3be-4729-a71b-ae988035f1e7
|
||||
18
src/core/annotation/Dom.annotation.ts
Normal file
18
src/core/annotation/Dom.annotation.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
36
src/core/annotation/Ioc.annotation.ts
Normal file
36
src/core/annotation/Ioc.annotation.ts
Normal 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
1
src/core/config/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
fbfd9291-1d66-4dcb-bcd8-abbe474d7c84
|
||||
22
src/core/config/ClassName.config.ts
Normal file
22
src/core/config/ClassName.config.ts
Normal 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"
|
||||
}
|
||||
9
src/core/config/VideoMimeType.config.ts
Normal file
9
src/core/config/VideoMimeType.config.ts
Normal 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',
|
||||
}
|
||||
61
src/core/config/index.config.ts
Normal file
61
src/core/config/index.config.ts
Normal 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
43
src/core/init.ts
Normal 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
1
src/core/model/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
3564163c-013d-4d8c-94bc-a306dd223191
|
||||
13
src/core/model/Ioc.model.ts
Normal file
13
src/core/model/Ioc.model.ts
Normal 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();
|
||||
24
src/core/model/OptionParams.model.ts
Normal file
24
src/core/model/OptionParams.model.ts
Normal 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
1
src/core/types/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
df13c4ef-91b6-4544-a480-23a706749d0a
|
||||
62
src/core/types/OptionParams.types.ts
Normal file
62
src/core/types/OptionParams.types.ts
Normal 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
1
src/core/ui/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
c86ae987-557c-4c45-ae05-8678ae14b9a5
|
||||
239
src/core/ui/controls.ui.ts
Normal file
239
src/core/ui/controls.ui.ts
Normal 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);
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/core/ui/coreSettings.ui.ts
Normal file
120
src/core/ui/coreSettings.ui.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
97
src/core/ui/eventListeners.ui.ts
Normal file
97
src/core/ui/eventListeners.ui.ts
Normal 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
38
src/core/ui/index.ui.ts
Normal 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
1
src/core/utils/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
b8fc9423-3e19-4e61-af70-8d7485eac391
|
||||
152
src/core/utils/index.utils.ts
Normal file
152
src/core/utils/index.utils.ts
Normal 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
56
src/index.pug
Normal 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
1
src/types/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
f6e0c4c0-5991-474c-a342-e583e8a5ba1c
|
||||
4
src/types/index.d.ts
vendored
Normal file
4
src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare interface ParentNode {
|
||||
querySelector(selectors: string): HTMLElement;
|
||||
}
|
||||
declare var window: Window & typeof globalThis;
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal 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
72
webpack.config.js
Normal 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'
|
||||
})
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user