阶段4:格式自动识别 + flv.js 替换为 mpegts.js + 移动端 5 项修复 + 自动化冒烟测试
- 按 URL 扩展名自动识别插件(.m3u8→hls.js,.flv/.ts/.m2ts→mpegts.js),iOS 上 m3u8 仍走原生 HLS - flv 播放改由 mpegts.js 承担,移除 flv.js 依赖,plug:'flv.js' 保留兼容别名 - 修复 plug:'dash.js' 调用不存在函数的死代码 bug - mpegts 能力判断由 mseLivePlayback 放宽为 isSupported()(修复部分浏览器点播被误杀) - 移动端:iOS 原生全屏状态同步、iPad/iPod UA 识别、X5 同层播放属性、进度/音量条触摸拖动、触摸坐标滚动偏移修正 - 新增 Playwright 冒烟测试(npm test)与支持 Range 的本地服务器(npm run serve) - 文档说明:测试禁用 python http.server(不支持 Range,mp4 无法 seek)
This commit is contained in:
@@ -33,8 +33,7 @@ MoePlayer —— 一款中文友好的开源网页视频播放器(MIT 协议
|
|||||||
│ ├── css/moeplayer.ixigua.css # 西瓜皮肤(浅色),同上
|
│ ├── css/moeplayer.ixigua.css # 西瓜皮肤(浅色),同上
|
||||||
│ ├── language/ # 语言包:zh.cn.js(默认内嵌)、zh.hk.js、en.js
|
│ ├── language/ # 语言包:zh.cn.js(默认内嵌)、zh.hk.js、en.js
|
||||||
│ ├── hls.js/ # hls.min.js,【由构建从 npm 包拷贝,勿手改】
|
│ ├── hls.js/ # hls.min.js,【由构建从 npm 包拷贝,勿手改】
|
||||||
│ ├── flv.js/ # flv.min.js,同上(flv.js 已停维护,阶段4将移除)
|
│ └── mpegts.js/ # mpegts.js,同上(flv 播放也基于它,flv.js 已于阶段4移除)
|
||||||
│ └── mpegts.js/ # mpegts.js,同上
|
|
||||||
└── video/ # 演示用视频与封面
|
└── video/ # 演示用视频与封面
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -44,23 +43,26 @@ MoePlayer —— 一款中文友好的开源网页视频播放器(MIT 协议
|
|||||||
- 构建:`npm run build`(即 `rollup -c && node scripts/build-css.js && node scripts/copy-libs.js`) —— 做三件事:
|
- 构建:`npm run build`(即 `rollup -c && node scripts/build-css.js && node scripts/copy-libs.js`) —— 做三件事:
|
||||||
1. rollup 把 `src/` 的 ES Module 打包为 UMD:`moeplayer/js/moeplayer.js`,并经 @rollup/plugin-terser 生成 `moeplayer.min.js`(保留 `软件名称|版权` 头部注释)
|
1. rollup 把 `src/` 的 ES Module 打包为 UMD:`moeplayer/js/moeplayer.js`,并经 @rollup/plugin-terser 生成 `moeplayer.min.js`(保留 `软件名称|版权` 头部注释)
|
||||||
2. `scripts/build-css.js` 把 `src/css/base.css` 与各主题(`src/css/themes/*.css`)拼接为自包含的 `moeplayer/css/moeplayer[.red|.ixigua].css`(加版权注释头,用户只引一个文件,无 @import)
|
2. `scripts/build-css.js` 把 `src/css/base.css` 与各主题(`src/css/themes/*.css`)拼接为自包含的 `moeplayer/css/moeplayer[.red|.ixigua].css`(加版权注释头,用户只引一个文件,无 @import)
|
||||||
3. `scripts/copy-libs.js` 从 node_modules 拷贝 hls.js / flv.js / mpegts.js 的产物及 LICENSE 到 `moeplayer/` 对应目录
|
3. `scripts/copy-libs.js` 从 node_modules 拷贝 hls.js / mpegts.js 的产物及 LICENSE 到 `moeplayer/` 对应目录
|
||||||
- **改动 `src/` 后必须执行 `npm run build`** 重新生成产物;`moeplayer/js/` 与 `moeplayer/css/` 下的 js、css 均为生成物,勿手改
|
- **改动 `src/` 后必须执行 `npm run build`** 重新生成产物;`moeplayer/js/` 与 `moeplayer/css/` 下的 js、css 均为生成物,勿手改
|
||||||
- 皮肤机制:全部主题相关值是 CSS 自定义属性(`--moe-*`),定义在 `.moeplayer, .moeplayer-menu, .moeplayer-error` 作用域(菜单/错误框挂在 body 下);按钮图标为 SVG data-URI + CSS mask,颜色跟随 `--moe-icon-color`,不再使用雪碧图 PNG;换肤=覆盖变量,新增主题=在 `src/css/themes/` 加一个变量文件并登记到 `scripts/build-css.js` 的 themes 清单
|
- 皮肤机制:全部主题相关值是 CSS 自定义属性(`--moe-*`),定义在 `.moeplayer, .moeplayer-menu, .moeplayer-error` 作用域(菜单/错误框挂在 body 下);按钮图标为 SVG data-URI + CSS mask,颜色跟随 `--moe-icon-color`,不再使用雪碧图 PNG;换肤=覆盖变量,新增主题=在 `src/css/themes/` 加一个变量文件并登记到 `scripts/build-css.js` 的 themes 清单
|
||||||
- 升级依赖:改 `package.json` 版本号后 `npm install && npm run build`
|
- 升级依赖:改 `package.json` 版本号后 `npm install && npm run build`
|
||||||
- 运行时机制:播放器用 `getPath()` 定位自身所在目录,按需动态 `<script>` 加载同级 `hls.js/` 等目录里的文件,库挂全局变量(`Hls`/`flvjs`/`mpegts`)。**这个目录结构约定不能破坏**
|
- 运行时机制:播放器用 `getPath()` 定位自身所在目录,按需动态 `<script>` 加载同级 `hls.js/` 等目录里的文件,库挂全局变量(`Hls`/`mpegts`)。**这个目录结构约定不能破坏**
|
||||||
|
- 插件机制:未显式指定 `plug` 时按 URL 扩展名自动识别插件(`.m3u8`→hls.js,`.flv`/`.ts`/`.m2ts`→mpegts.js,识别不到走原生播放),只填 `vars['plug']`,canPlay 原生分流不变(iOS 上 m3u8 仍走原生 HLS);flv 播放基于 mpegts.js,`plug:'flv.js'` 保留为兼容别名;`plug:'dash.js'` 已移除
|
||||||
- rollup 配置要点:`treeshake: false`(完整保留所有工具函数,与旧单文件一致);UMD `name: 'MoePlayer'`、`exports: 'default'`,CJS/AMD/全局变量行为与旧 UMD 等价
|
- rollup 配置要点:`treeshake: false`(完整保留所有工具函数,与旧单文件一致);UMD `name: 'MoePlayer'`、`exports: 'default'`,CJS/AMD/全局变量行为与旧 UMD 等价
|
||||||
|
|
||||||
## 测试方式
|
## 测试方式
|
||||||
|
|
||||||
没有自动化测试。手动验证:在本目录起静态服务器(`python3 -m http.server`),打开 `index.html`,用页面上的播放/暂停/seek/全屏等按钮回归核心功能。(Playwright 浏览器自动化测试在后续阶段引入)
|
- 自动化冒烟测试:`npm test`(需先 `npm run build`)——Playwright 无头 Chrome 加载演示页,覆盖初始化/播放/暂停/seek/进度条点击/皮肤加载/JS 错误,脚本在 `scripts/smoke.test.js`
|
||||||
|
- 手动回归:`npm run serve` 起本地服务器(scripts/serve.js,支持 Range 请求),打开 `http://localhost:8000`。**不要用 `python3 -m http.server`——它不支持 Range 请求,会导致 mp4 无法 seek(点击/拖动进度条静默失效)**
|
||||||
|
- 移动端回归:iOS Safari 与微信内置浏览器各过一遍全屏与进度拖动(目前需真机手动)
|
||||||
|
|
||||||
## 代码风格
|
## 代码风格
|
||||||
|
|
||||||
- 注释与文档使用中文(项目惯例)
|
- 注释与文档使用中文(项目惯例)
|
||||||
- `src/` 为 ES Module 源码,但函数体保持旧单文件的 ES5 风格(`var`、函数式),阶段 2/3 重构前不要在函数体内引入 ES6+ 语法;只允许模块级的 `import`/`export` 语句
|
- `src/` 为 ES Module 源码,但函数体保持旧单文件的 ES5 风格(`var`、函数式),阶段 2/3 重构前不要在函数体内引入 ES6+ 语法;只允许模块级的 `import`/`export` 语句
|
||||||
- 主函数 `moeplayerEmbed` 整体搬迁、内部逻辑零改动;语言包替换机制(`window.moeplayerLanguage`)在其内部,勿动
|
- 主函数 `moeplayerEmbed` 整体搬迁、内部逻辑零改动;语言包替换机制(`window.moeplayerLanguage`)在其内部,勿动
|
||||||
- `moeplayer/` 下由构建生成的文件(js 两个产物、css 三个皮肤、hls.js/flv.js/mpegts.js 目录内容)不要手动修改
|
- `moeplayer/` 下由构建生成的文件(js 两个产物、css 三个皮肤、hls.js/mpegts.js 目录内容)不要手动修改
|
||||||
|
|
||||||
## Git 约定
|
## Git 约定
|
||||||
|
|
||||||
@@ -72,6 +74,6 @@ MoePlayer —— 一款中文友好的开源网页视频播放器(MIT 协议
|
|||||||
1. ~~工程化:npm + 构建脚本 + 依赖 npm 化~~(已完成)
|
1. ~~工程化:npm + 构建脚本 + 依赖 npm 化~~(已完成)
|
||||||
2. 改名 ckplayer → MoePlayer(已完成)+ 单文件拆分为 ES Module(已完成,`src/` + rollup 打包;主函数 `moeplayerEmbed` 内部尚未拆分)+ README 重写
|
2. 改名 ckplayer → MoePlayer(已完成)+ 单文件拆分为 ES Module(已完成,`src/` + rollup 打包;主函数 `moeplayerEmbed` 内部尚未拆分)+ README 重写
|
||||||
3. 核心与 UI 分层、皮肤改用 CSS 变量 + SVG 图标
|
3. 核心与 UI 分层、皮肤改用 CSS 变量 + SVG 图标
|
||||||
4. 协议插件化(hls/mpegts 按需加载,移除 flv.js)、移动端 H5 兼容专项
|
4. ~~协议插件化:按 URL 自动识别 m3u8/flv/ts 插件、flv 改用 mpegts.js、移除 flv.js~~(已完成);移动端 H5 兼容专项(审查已完成,修复待排期)
|
||||||
5. Moodle media 插件(独立仓库 moodle-media_moeplayer)
|
5. Moodle media 插件(独立仓库 moodle-media_moeplayer)
|
||||||
6. 发布:Gitea 主仓库 + 中文文档站
|
6. 发布:Gitea 主仓库 + 中文文档站
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
- 支持 mp4 / flv / m3u8 / ts 等格式(m3u8/flv/ts 基于 hls.js、flv.js、mpegts.js)
|
- 支持 mp4 / flv / m3u8 / ts 等格式(m3u8 基于 hls.js,flv/ts 基于 mpegts.js)
|
||||||
|
- 无需指定插件,按 URL 扩展名自动识别 m3u8 / flv / ts(m2ts);iOS 上 m3u8 自动走原生 HLS
|
||||||
- 支持点播、直播、直播+回放
|
- 支持点播、直播、直播+回放
|
||||||
- 支持 PC 与移动端 H5
|
- 支持 PC 与移动端 H5
|
||||||
- 支持使用 JS 监听播放器各种状态、控制播放器各种操作
|
- 支持使用 JS 监听播放器各种状态、控制播放器各种操作
|
||||||
@@ -51,15 +52,15 @@ npm install
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
源码在 `src/`(ES Module),构建产物输出到 `moeplayer/js/`(UMD,含未压缩版与 min 版),同时从 npm 依赖拷贝 hls.js / flv.js / mpegts.js 运行时库到 `moeplayer/` 对应目录。
|
源码在 `src/`(ES Module),构建产物输出到 `moeplayer/js/`(UMD,含未压缩版与 min 版),同时从 npm 依赖拷贝 hls.js / mpegts.js 运行时库到 `moeplayer/` 对应目录。
|
||||||
|
|
||||||
## 改造路线图
|
## 改造路线图
|
||||||
|
|
||||||
- [x] 工程化:npm + rollup 构建,依赖 npm 化管理
|
- [x] 工程化:npm + rollup 构建,依赖 npm 化管理
|
||||||
- [x] 改名 ckplayer → MoePlayer,单文件拆分为 ES Module
|
- [x] 改名 ckplayer → MoePlayer,单文件拆分为 ES Module
|
||||||
- [ ] 核心与 UI 分层,皮肤改用 CSS 变量 + SVG 图标
|
- [ ] 核心与 UI 分层,皮肤改用 CSS 变量 + SVG 图标
|
||||||
- [ ] 协议插件化(hls/mpegts 按需加载,移除已停维护的 flv.js)
|
- [x] 协议插件化:按 URL 自动识别 m3u8/flv/ts 插件,flv 改用 mpegts.js(移除已停维护的 flv.js)
|
||||||
- [ ] 移动端 H5 兼容性专项
|
- [ ] 移动端 H5 兼容性专项(审查已完成,修复待排期)
|
||||||
- [ ] Moodle media 插件(独立仓库 moodle-media_moeplayer)
|
- [ ] Moodle media 插件(独立仓库 moodle-media_moeplayer)
|
||||||
- [ ] 完整中文文档
|
- [ ] 完整中文文档
|
||||||
|
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
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.
|
|
||||||
Vendored
-10
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+113
-19
@@ -624,11 +624,15 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* getApp
|
* getApp
|
||||||
* 功能,获取平台类型是否是iphone
|
* 功能,获取平台类型是否是iphone(含ipod/ipad,统一返回'iphone'以保持现有分支兼容)
|
||||||
*/
|
*/
|
||||||
function getApp(){
|
function getApp(){
|
||||||
var u = navigator.userAgent.toLowerCase();
|
var u = navigator.userAgent.toLowerCase();
|
||||||
if(u.indexOf('iphone')>-1){
|
if(u.indexOf('iphone')>-1 || u.indexOf('ipod')>-1 || u.indexOf('ipad')>-1){
|
||||||
|
return 'iphone';
|
||||||
|
}
|
||||||
|
//iPadOS 13+默认使用桌面UA(Macintosh),通过MacIntel平台+触屏点数判断
|
||||||
|
if(u.indexOf('macintosh')>-1 && navigator.platform=='MacIntel' && navigator.maxTouchPoints>1){
|
||||||
return 'iphone';
|
return 'iphone';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,6 +1027,8 @@
|
|||||||
var CT=null,CK=null,CM=null,CV=null;//CT=播放器容器,页面中已存的,CT>CK>CM>CV>video
|
var CT=null,CK=null,CM=null,CV=null;//CT=播放器容器,页面中已存的,CT>CK>CM>CV>video
|
||||||
var pugPlayer=null;//插件播放器
|
var pugPlayer=null;//插件播放器
|
||||||
var hls=null;//播放hls
|
var hls=null;//播放hls
|
||||||
|
var plugAuto='';//自动识别设置的插件名,用于与用户显式指定的plug区分
|
||||||
|
var videoNativeFull=false;//video元素是否处于原生全屏(iOS Safari的webkitEnterFullscreen)
|
||||||
var loadMeta=true;//第一次加载到元数据
|
var loadMeta=true;//第一次加载到元数据
|
||||||
var app='';//平台类型
|
var app='';//平台类型
|
||||||
var nowRotate=0;//当前视频旋转角度
|
var nowRotate=0;//当前视频旋转角度
|
||||||
@@ -1303,6 +1309,8 @@
|
|||||||
video.attr('playsinline','true');
|
video.attr('playsinline','true');
|
||||||
video.attr('webkit-playsinline','true');
|
video.attr('webkit-playsinline','true');
|
||||||
video.attr('x5-playsinline','true');
|
video.attr('x5-playsinline','true');
|
||||||
|
video.attr('x5-video-player-type','h5');//X5内核启用同层H5播放
|
||||||
|
video.attr('x5-video-player-fullscreen','true');//X5内核全屏时使用同层播放器
|
||||||
}
|
}
|
||||||
if(vars['crossOrigin']){
|
if(vars['crossOrigin']){
|
||||||
video.useCORS=true;//解决跨域
|
video.useCORS=true;//解决跨域
|
||||||
@@ -1430,6 +1438,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
/*
|
||||||
|
* 未指定插件(或插件为上次自动识别的结果)时,按视频地址扩展名自动识别插件
|
||||||
|
* 仅填写vars['plug'],后续canPlay分流逻辑不变:iOS等原生支持m3u8的环境仍走原生播放
|
||||||
|
*/
|
||||||
|
if(!vars['plug'] || vars['plug']==plugAuto){
|
||||||
|
vars['plug']=getPlugByUrl(vstr);
|
||||||
|
plugAuto=vars['plug'];
|
||||||
|
}
|
||||||
if(vars['plug'] && !canPlay(vstr)){
|
if(vars['plug'] && !canPlay(vstr)){
|
||||||
plugPlayer(vstr);
|
plugPlayer(vstr);
|
||||||
}
|
}
|
||||||
@@ -1532,9 +1548,6 @@
|
|||||||
case 'mpegts.js':
|
case 'mpegts.js':
|
||||||
mpegtsPlayer(url);
|
mpegtsPlayer(url);
|
||||||
break;
|
break;
|
||||||
case 'dash.js':
|
|
||||||
dashPlayer(url);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if(valType(vars['plug'])=='function'){
|
if(valType(vars['plug'])=='function'){
|
||||||
vars['plug'](video,url);
|
vars['plug'](video,url);
|
||||||
@@ -1590,12 +1603,12 @@
|
|||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* flvPlayer
|
* flvPlayer
|
||||||
* 功能:使用flv.js插件进行播放
|
* 功能:播放flv格式视频(基于mpegts.js库,plug:'flv.js'为兼容旧配置的别名)
|
||||||
*/
|
*/
|
||||||
flvPlayer=function (url){
|
flvPlayer=function (url){
|
||||||
var path=getPath('flv.js')+'flv.min.js';
|
var path=getPath('mpegts.js')+'mpegts.js';
|
||||||
loadJs(path,function(){
|
loadJs(path,function(){
|
||||||
if (typeof(flvjs)!='undefined' && !isUndefined(flvjs) && flvjs.isSupported()) {
|
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||||
var config={
|
var config={
|
||||||
type: 'flv',
|
type: 'flv',
|
||||||
url: url
|
url: url
|
||||||
@@ -1613,13 +1626,13 @@
|
|||||||
pugPlayer.destroy();
|
pugPlayer.destroy();
|
||||||
pugPlayer=null;
|
pugPlayer=null;
|
||||||
}
|
}
|
||||||
pugPlayer = flvjs.createPlayer(config);
|
pugPlayer = mpegts.createPlayer(config);
|
||||||
pugPlayer.attachMediaElement(video);
|
pugPlayer.attachMediaElement(video);
|
||||||
pugPlayer.load();
|
pugPlayer.load();
|
||||||
pugPlayer.on(flvjs.Events.METADATA_ARRIVED, function(res){
|
pugPlayer.on(mpegts.Events.METADATA_ARRIVED, function(res){
|
||||||
videoHandler.loadedMetaData();
|
videoHandler.loadedMetaData();
|
||||||
});
|
});
|
||||||
pugPlayer.on(flvjs.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
pugPlayer.on(mpegts.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
||||||
CT.error={code:errorInfo['code'],message:errorInfo['msg']};
|
CT.error={code:errorInfo['code'],message:errorInfo['msg']};
|
||||||
eventTarget('error',CT.error);//注册监听error
|
eventTarget('error',CT.error);//注册监听error
|
||||||
showError();
|
showError();
|
||||||
@@ -1634,7 +1647,7 @@
|
|||||||
mpegtsPlayer=function (url){
|
mpegtsPlayer=function (url){
|
||||||
var path=getPath('mpegts.js')+'mpegts.js';
|
var path=getPath('mpegts.js')+'mpegts.js';
|
||||||
loadJs(path,function(){
|
loadJs(path,function(){
|
||||||
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.getFeatureList().mseLivePlayback) {
|
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||||
var config={
|
var config={
|
||||||
type: 'mse',
|
type: 'mse',
|
||||||
url: url
|
url: url
|
||||||
@@ -1667,6 +1680,29 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
|
* getPlugByUrl
|
||||||
|
* 功能:根据视频地址的扩展名自动识别需要使用的插件
|
||||||
|
* 识别不到时返回''(维持原生播放路径),URL支持携带query/hash参数
|
||||||
|
*/
|
||||||
|
getPlugByUrl=function(url){
|
||||||
|
if(valType(url)!='string'){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var purePath=url.split('?')[0].split('#')[0];
|
||||||
|
var ext=purePath.substring(purePath.lastIndexOf('.')+1).toLowerCase();
|
||||||
|
switch(ext){
|
||||||
|
case 'm3u8':
|
||||||
|
return 'hls.js';
|
||||||
|
case 'flv':
|
||||||
|
return 'mpegts.js';
|
||||||
|
case 'ts':
|
||||||
|
case 'm2ts':
|
||||||
|
return 'mpegts.js';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
* canPlay
|
* canPlay
|
||||||
* 功能:判断是否能支持相关视频格式
|
* 功能:判断是否能支持相关视频格式
|
||||||
@@ -1706,7 +1742,9 @@
|
|||||||
return sup;
|
return sup;
|
||||||
};
|
};
|
||||||
var getExtension=function(filepath) {
|
var getExtension=function(filepath) {
|
||||||
return filepath.replace(/.+\./, '');
|
//先剥离query与hash,避免 xxx.m3u8?token=... 之类地址无法识别扩展名
|
||||||
|
var purePath=filepath.split('?')[0].split('#')[0];
|
||||||
|
return purePath.replace(/.+\./, '');
|
||||||
};
|
};
|
||||||
if(isUndefined(num)){
|
if(isUndefined(num)){
|
||||||
if(valType(vStr)=='string'){
|
if(valType(vStr)=='string'){
|
||||||
@@ -2189,6 +2227,24 @@
|
|||||||
CM.doubleClick(player.fullOrExit);//监听视频双击
|
CM.doubleClick(player.fullOrExit);//监听视频双击
|
||||||
$(document).addListener('keydown',videoHandler.keydown);//监听键盘按键
|
$(document).addListener('keydown',videoHandler.keydown);//监听键盘按键
|
||||||
addListener(window, 'resize', videoHandler.resize);//监听窗口尺寸变化
|
addListener(window, 'resize', videoHandler.resize);//监听窗口尺寸变化
|
||||||
|
/*
|
||||||
|
* 监听video元素原生全屏的进出(iOS Safari),同步内部全屏状态
|
||||||
|
*/
|
||||||
|
video.addListener('webkitbeginfullscreen',function(){
|
||||||
|
videoNativeFull=true;
|
||||||
|
videoHandler.resize();
|
||||||
|
});
|
||||||
|
video.addListener('webkitendfullscreen',function(){
|
||||||
|
videoNativeFull=false;
|
||||||
|
videoHandler.resize();
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* 监听标准全屏状态变化(桌面端),保持全屏状态准确
|
||||||
|
*/
|
||||||
|
addListener(document,'fullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'webkitfullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'mozfullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'MSFullscreenChange',videoHandler.resize);
|
||||||
if(!isUndefined(vars['smallWindows'])){
|
if(!isUndefined(vars['smallWindows'])){
|
||||||
if(valType(vars['smallWindows'])=='boolean' && vars['smallWindows']){
|
if(valType(vars['smallWindows'])=='boolean' && vars['smallWindows']){
|
||||||
addListener(window, 'scroll', windowScroll);//监听窗口滚动
|
addListener(window, 'scroll', windowScroll);//监听窗口滚动
|
||||||
@@ -2733,7 +2789,7 @@
|
|||||||
replaceInformation('volume',parseInt(this.volume*100));
|
replaceInformation('volume',parseInt(this.volume*100));
|
||||||
},
|
},
|
||||||
resize:function(){
|
resize:function(){
|
||||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||||
if(fullState) {
|
if(fullState) {
|
||||||
C['bar']['fullAndExit']['exitFull'].show();
|
C['bar']['fullAndExit']['exitFull'].show();
|
||||||
C['bar']['fullAndExit']['full'].hide();
|
C['bar']['fullAndExit']['full'].hide();
|
||||||
@@ -3856,6 +3912,11 @@
|
|||||||
* 功能: 退出全屏
|
* 功能: 退出全屏
|
||||||
*/
|
*/
|
||||||
exitFull:function(){
|
exitFull:function(){
|
||||||
|
//iOS Safari上video处于原生全屏时,通过video.webkitExitFullscreen退出
|
||||||
|
if(videoNativeFull && !isUndefined(video.webkitExitFullscreen)){
|
||||||
|
video.webkitExitFullscreen();
|
||||||
|
return player;
|
||||||
|
}
|
||||||
var exitFullFun = document.exitFullscreen || //W3C
|
var exitFullFun = document.exitFullscreen || //W3C
|
||||||
document.mozCancelFullScreen || //FireFox
|
document.mozCancelFullScreen || //FireFox
|
||||||
document.webkitExitFullscreen || //Chrome等
|
document.webkitExitFullscreen || //Chrome等
|
||||||
@@ -3877,7 +3938,7 @@
|
|||||||
* 功能:在全屏和退出全屏之间进行切换
|
* 功能:在全屏和退出全屏之间进行切换
|
||||||
*/
|
*/
|
||||||
fullOrExit:function(){
|
fullOrExit:function(){
|
||||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||||
if(fullState){
|
if(fullState){
|
||||||
player.exitFull();
|
player.exitFull();
|
||||||
}
|
}
|
||||||
@@ -5892,6 +5953,16 @@
|
|||||||
bg.mousemove(bgMouseMove);
|
bg.mousemove(bgMouseMove);
|
||||||
bg.mouseout(bgMouseOut);
|
bg.mouseout(bgMouseOut);
|
||||||
mouseLine.mouseover(clearTime);
|
mouseLine.mouseover(clearTime);
|
||||||
|
/*
|
||||||
|
* 移动端:支持在进度条上按住拖动seek,复用bgMouseDown,preventDefault防止页面滚动
|
||||||
|
*/
|
||||||
|
var bgTouchSeek=function(e){
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
bgMouseDown(e);
|
||||||
|
};
|
||||||
|
bg.touchstart(bgTouchSeek);
|
||||||
|
bg.touchmove(bgTouchSeek);
|
||||||
|
bg.touchend(bgTouchSeek);
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* volumeDragY
|
* volumeDragY
|
||||||
@@ -5967,6 +6038,16 @@
|
|||||||
player.volume(vol);
|
player.volume(vol);
|
||||||
};
|
};
|
||||||
bg.mousedown(bgMouseDown);
|
bg.mousedown(bgMouseDown);
|
||||||
|
/*
|
||||||
|
* 移动端:支持在音量条上按住拖动调节音量,复用bgMouseDown,preventDefault防止页面滚动
|
||||||
|
*/
|
||||||
|
var bgTouchVolume=function(e){
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
bgMouseDown(e);
|
||||||
|
};
|
||||||
|
bg.touchstart(bgTouchVolume);
|
||||||
|
bg.touchmove(bgTouchVolume);
|
||||||
|
bg.touchend(bgTouchVolume);
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* changeProgress
|
* changeProgress
|
||||||
@@ -7279,11 +7360,24 @@
|
|||||||
}
|
}
|
||||||
var x=eve.clientX + (document.documentElement.scrollLeft || $('body').scrollLeft),
|
var x=eve.clientX + (document.documentElement.scrollLeft || $('body').scrollLeft),
|
||||||
y=eve.clientY + (document.documentElement.scrollTop || $('body').scrollTop);
|
y=eve.clientY + (document.documentElement.scrollTop || $('body').scrollTop);
|
||||||
if(isUndefined(x.toString()) || x.toString()=='NaN'){
|
if(isUndefined(x.toString()) || x.toString()=='NaN' || isUndefined(y.toString()) || y.toString()=='NaN'){
|
||||||
x=eve.touches[0].clientX;
|
//触摸事件:touchstart/touchmove取touches;touchend时touches为空,取changedTouches兜底
|
||||||
|
var touch=null;
|
||||||
|
if(!isUndefined(eve.touches) && eve.touches.length>0){
|
||||||
|
touch=eve.touches[0];
|
||||||
|
}
|
||||||
|
else if(!isUndefined(eve.changedTouches) && eve.changedTouches.length>0){
|
||||||
|
touch=eve.changedTouches[0];
|
||||||
|
}
|
||||||
|
if(touch){
|
||||||
|
//与mouse路径对齐,补上页面滚动偏移
|
||||||
|
x=touch.clientX + (window.pageXOffset || document.documentElement.scrollLeft || 0);
|
||||||
|
y=touch.clientY + (window.pageYOffset || document.documentElement.scrollTop || 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
x=0;
|
||||||
|
y=0;
|
||||||
}
|
}
|
||||||
if(isUndefined(y.toString()) || y.toString()=='NaN'){
|
|
||||||
y=eve.touches[0].clientY;
|
|
||||||
}
|
}
|
||||||
if(x.toString()=='NaN'){
|
if(x.toString()=='NaN'){
|
||||||
x=0;
|
x=0;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Generated
+48
-11
@@ -9,13 +9,13 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flv.js": "^1.6.2",
|
|
||||||
"hls.js": "^1.6.10",
|
"hls.js": "^1.6.10",
|
||||||
"mpegts.js": "^1.8.0"
|
"mpegts.js": "^1.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-replace": "^6.0.3",
|
"@rollup/plugin-replace": "^6.0.3",
|
||||||
"@rollup/plugin-terser": "^1.0.0",
|
"@rollup/plugin-terser": "^1.0.0",
|
||||||
|
"playwright": "^1.61.1",
|
||||||
"rollup": "^4.62.2"
|
"rollup": "^4.62.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -573,16 +573,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/flv.js": {
|
|
||||||
"version": "1.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/flv.js/-/flv.js-1.6.2.tgz",
|
|
||||||
"integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"es6-promise": "^4.2.8",
|
|
||||||
"webworkify-webpack": "^2.1.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -637,6 +627,53 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.61.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||||
|
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.61.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.61.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||||
|
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.62.2",
|
"version": "4.62.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
||||||
|
|||||||
+4
-2
@@ -4,16 +4,18 @@
|
|||||||
"description": "一款中文友好的网页视频播放器(fork 自 ckplayer X3),支持 mp4/flv/m3u8/ts,可搭配 Moodle media 插件使用",
|
"description": "一款中文友好的网页视频播放器(fork 自 ckplayer X3),支持 mp4/flv/m3u8/ts,可搭配 Moodle media 插件使用",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c && node scripts/build-css.js && node scripts/copy-libs.js"
|
"build": "rollup -c && node scripts/build-css.js && node scripts/copy-libs.js",
|
||||||
|
"test": "node scripts/smoke.test.js",
|
||||||
|
"serve": "node scripts/serve.js 8000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flv.js": "^1.6.2",
|
|
||||||
"hls.js": "^1.6.10",
|
"hls.js": "^1.6.10",
|
||||||
"mpegts.js": "^1.8.0"
|
"mpegts.js": "^1.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-replace": "^6.0.3",
|
"@rollup/plugin-replace": "^6.0.3",
|
||||||
"@rollup/plugin-terser": "^1.0.0",
|
"@rollup/plugin-terser": "^1.0.0",
|
||||||
|
"playwright": "^1.61.1",
|
||||||
"rollup": "^4.62.2"
|
"rollup": "^4.62.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* 构建脚本(第二步)
|
* 构建脚本(第二步)
|
||||||
* 功能:从 node_modules 拷贝运行时依赖(hls.js/flv.js/mpegts.js)到 moeplayer/ 对应目录
|
* 功能:从 node_modules 拷贝运行时依赖(hls.js/mpegts.js)到 moeplayer/ 对应目录
|
||||||
* 播放器运行时会按需动态加载这些文件,目录结构需与 getPath() 的约定保持一致
|
* 播放器运行时会按需动态加载这些文件,目录结构需与 getPath() 的约定保持一致
|
||||||
* 第一步(rollup 打包压缩)见 rollup.config.js
|
* 第一步(rollup 打包压缩)见 rollup.config.js
|
||||||
* 用法:npm run build(即 rollup -c && node scripts/copy-libs.js)
|
* 用法:npm run build(即 rollup -c && node scripts/copy-libs.js)
|
||||||
@@ -13,7 +13,6 @@ const root = path.resolve(__dirname, '..');
|
|||||||
// 运行时依赖拷贝清单:[npm包名, 产物目录, 需要拷贝的文件]
|
// 运行时依赖拷贝清单:[npm包名, 产物目录, 需要拷贝的文件]
|
||||||
const libs = [
|
const libs = [
|
||||||
['hls.js', 'hls.js', ['hls.min.js', 'hls.min.js.map', 'LICENSE']],
|
['hls.js', 'hls.js', ['hls.min.js', 'hls.min.js.map', 'LICENSE']],
|
||||||
['flv.js', 'flv.js', ['flv.min.js', 'flv.min.js.map', 'LICENSE']],
|
|
||||||
['mpegts.js', 'mpegts.js', ['mpegts.js', 'mpegts.js.map', 'LICENSE']]
|
['mpegts.js', 'mpegts.js', ['mpegts.js', 'mpegts.js.map', 'LICENSE']]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 支持 Range 请求的静态文件服务器(测试用)
|
||||||
|
* python3 -m http.server 不支持 Range,会导致 mp4 无法 seek,故用此替代
|
||||||
|
* 用法:node scripts/serve.js [端口]
|
||||||
|
*/
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
const port = parseInt(process.argv[2] || '8931', 10);
|
||||||
|
const mime = {
|
||||||
|
'.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
|
||||||
|
'.png': 'image/png', '.mp4': 'video/mp4', '.json': 'application/json',
|
||||||
|
'.map': 'application/json', '.ico': 'image/x-icon'
|
||||||
|
};
|
||||||
|
|
||||||
|
http.createServer((req, res) => {
|
||||||
|
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
||||||
|
let file = path.join(root, urlPath === '/' ? 'index.html' : urlPath);
|
||||||
|
if (!file.startsWith(root)) { res.writeHead(403); res.end(); return; }
|
||||||
|
fs.stat(file, (err, stat) => {
|
||||||
|
if (err || !stat.isFile()) { res.writeHead(404); res.end('404'); return; }
|
||||||
|
const type = mime[path.extname(file).toLowerCase()] || 'application/octet-stream';
|
||||||
|
const range = req.headers.range;
|
||||||
|
if (range) {
|
||||||
|
const m = /bytes=(\d*)-(\d*)/.exec(range);
|
||||||
|
let start = m && m[1] ? parseInt(m[1], 10) : 0;
|
||||||
|
let end = m && m[2] ? parseInt(m[2], 10) : stat.size - 1;
|
||||||
|
end = Math.min(end, stat.size - 1);
|
||||||
|
res.writeHead(206, {
|
||||||
|
'Content-Type': type,
|
||||||
|
'Content-Range': `bytes ${start}-${end}/${stat.size}`,
|
||||||
|
'Accept-Ranges': 'bytes',
|
||||||
|
'Content-Length': end - start + 1
|
||||||
|
});
|
||||||
|
fs.createReadStream(file, { start, end }).pipe(res);
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, { 'Content-Type': type, 'Accept-Ranges': 'bytes', 'Content-Length': stat.size });
|
||||||
|
fs.createReadStream(file).pipe(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).listen(port, () => console.log(`静态服务器(支持 Range): http://localhost:${port}`));
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 冒烟测试:真实浏览器加载演示页,验证核心功能
|
||||||
|
* 覆盖:播放器初始化、播放/暂停、seek 跳转、点击进度条、皮肤文件加载
|
||||||
|
* 用法:npm test(需要先 npm run build)
|
||||||
|
*/
|
||||||
|
const { chromium } = require('playwright');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const PORT = 8931;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
function check(name, cond, detail) {
|
||||||
|
if (cond) {
|
||||||
|
console.log(' ✓ ' + name);
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
console.error(' ✗ ' + name + (detail ? ' —— ' + detail : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const server = spawn('node', [path.join(__dirname, 'serve.js'), String(PORT)], { stdio: 'ignore' });
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', e => errors.push(e.message));
|
||||||
|
|
||||||
|
await page.goto(`http://localhost:${PORT}/index.html`);
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
const video = () => page.evaluate(() => {
|
||||||
|
const v = document.querySelector('video');
|
||||||
|
return v ? { duration: v.duration, currentTime: v.currentTime, paused: v.paused } : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('初始化');
|
||||||
|
check('全局 MoePlayer 存在', await page.evaluate(() => typeof window.MoePlayer === 'function'));
|
||||||
|
const v0 = await video();
|
||||||
|
check('video 元素已创建', !!v0);
|
||||||
|
check('元数据已加载(duration>0)', v0 && v0.duration > 0, JSON.stringify(v0));
|
||||||
|
|
||||||
|
console.log('播放控制');
|
||||||
|
await page.evaluate(() => player.play());
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
const v1 = await video();
|
||||||
|
check('播放中', v1 && !v1.paused && v1.currentTime > 0.5, JSON.stringify(v1));
|
||||||
|
await page.evaluate(() => player.pause());
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
check('暂停', (await video()).paused);
|
||||||
|
|
||||||
|
console.log('seek');
|
||||||
|
await page.evaluate(() => player.seek(20));
|
||||||
|
await page.waitForTimeout(800);
|
||||||
|
const v2 = await video();
|
||||||
|
check('seek(20) 生效', v2.currentTime >= 19 && v2.currentTime <= 22, 'currentTime=' + v2.currentTime);
|
||||||
|
|
||||||
|
console.log('进度条点击');
|
||||||
|
const rect = await page.evaluate(() => {
|
||||||
|
const r = document.querySelector('.moe-bar-progress-bg').getBoundingClientRect();
|
||||||
|
return { x: r.x, y: r.y, w: r.width, h: r.height };
|
||||||
|
});
|
||||||
|
await page.mouse.click(rect.x + rect.w * 0.7, rect.y + rect.h / 2);
|
||||||
|
await page.waitForTimeout(800);
|
||||||
|
const v3 = await video();
|
||||||
|
const expect3 = v0.duration * 0.7;
|
||||||
|
check('点击 70% 位置跳转', Math.abs(v3.currentTime - expect3) < 2.5, `currentTime=${v3.currentTime},期望≈${expect3}`);
|
||||||
|
|
||||||
|
console.log('皮肤');
|
||||||
|
for (const css of ['moeplayer.css', 'moeplayer.red.css', 'moeplayer.ixigua.css']) {
|
||||||
|
const resp = await page.request.get(`http://localhost:${PORT}/moeplayer/css/${css}`);
|
||||||
|
check(css + ' 可加载', resp.status() === 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
check('无页面 JS 错误', errors.length === 0, errors.join(' | '));
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
server.kill();
|
||||||
|
console.log(failed === 0 ? '\n全部通过' : `\n${failed} 项失败`);
|
||||||
|
process.exit(failed === 0 ? 0 : 1);
|
||||||
|
})().catch(e => { console.error(e); process.exit(1); });
|
||||||
+107
-17
@@ -59,6 +59,8 @@ var language = languageDefault;
|
|||||||
var CT=null,CK=null,CM=null,CV=null;//CT=播放器容器,页面中已存的,CT>CK>CM>CV>video
|
var CT=null,CK=null,CM=null,CV=null;//CT=播放器容器,页面中已存的,CT>CK>CM>CV>video
|
||||||
var pugPlayer=null;//插件播放器
|
var pugPlayer=null;//插件播放器
|
||||||
var hls=null;//播放hls
|
var hls=null;//播放hls
|
||||||
|
var plugAuto='';//自动识别设置的插件名,用于与用户显式指定的plug区分
|
||||||
|
var videoNativeFull=false;//video元素是否处于原生全屏(iOS Safari的webkitEnterFullscreen)
|
||||||
var loadMeta=true;//第一次加载到元数据
|
var loadMeta=true;//第一次加载到元数据
|
||||||
var app='';//平台类型
|
var app='';//平台类型
|
||||||
var nowRotate=0;//当前视频旋转角度
|
var nowRotate=0;//当前视频旋转角度
|
||||||
@@ -339,6 +341,8 @@ var language = languageDefault;
|
|||||||
video.attr('playsinline','true');
|
video.attr('playsinline','true');
|
||||||
video.attr('webkit-playsinline','true');
|
video.attr('webkit-playsinline','true');
|
||||||
video.attr('x5-playsinline','true');
|
video.attr('x5-playsinline','true');
|
||||||
|
video.attr('x5-video-player-type','h5');//X5内核启用同层H5播放
|
||||||
|
video.attr('x5-video-player-fullscreen','true');//X5内核全屏时使用同层播放器
|
||||||
}
|
}
|
||||||
if(vars['crossOrigin']){
|
if(vars['crossOrigin']){
|
||||||
video.useCORS=true;//解决跨域
|
video.useCORS=true;//解决跨域
|
||||||
@@ -466,6 +470,14 @@ var language = languageDefault;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
/*
|
||||||
|
* 未指定插件(或插件为上次自动识别的结果)时,按视频地址扩展名自动识别插件
|
||||||
|
* 仅填写vars['plug'],后续canPlay分流逻辑不变:iOS等原生支持m3u8的环境仍走原生播放
|
||||||
|
*/
|
||||||
|
if(!vars['plug'] || vars['plug']==plugAuto){
|
||||||
|
vars['plug']=getPlugByUrl(vstr);
|
||||||
|
plugAuto=vars['plug'];
|
||||||
|
}
|
||||||
if(vars['plug'] && !canPlay(vstr)){
|
if(vars['plug'] && !canPlay(vstr)){
|
||||||
plugPlayer(vstr);
|
plugPlayer(vstr);
|
||||||
}
|
}
|
||||||
@@ -568,9 +580,6 @@ var language = languageDefault;
|
|||||||
case 'mpegts.js':
|
case 'mpegts.js':
|
||||||
mpegtsPlayer(url);
|
mpegtsPlayer(url);
|
||||||
break;
|
break;
|
||||||
case 'dash.js':
|
|
||||||
dashPlayer(url);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if(valType(vars['plug'])=='function'){
|
if(valType(vars['plug'])=='function'){
|
||||||
vars['plug'](video,url);
|
vars['plug'](video,url);
|
||||||
@@ -626,12 +635,12 @@ var language = languageDefault;
|
|||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* flvPlayer
|
* flvPlayer
|
||||||
* 功能:使用flv.js插件进行播放
|
* 功能:播放flv格式视频(基于mpegts.js库,plug:'flv.js'为兼容旧配置的别名)
|
||||||
*/
|
*/
|
||||||
flvPlayer=function (url){
|
flvPlayer=function (url){
|
||||||
var path=getPath('flv.js')+'flv.min.js';
|
var path=getPath('mpegts.js')+'mpegts.js';
|
||||||
loadJs(path,function(){
|
loadJs(path,function(){
|
||||||
if (typeof(flvjs)!='undefined' && !isUndefined(flvjs) && flvjs.isSupported()) {
|
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||||
var config={
|
var config={
|
||||||
type: 'flv',
|
type: 'flv',
|
||||||
url: url
|
url: url
|
||||||
@@ -649,13 +658,13 @@ var language = languageDefault;
|
|||||||
pugPlayer.destroy();
|
pugPlayer.destroy();
|
||||||
pugPlayer=null;
|
pugPlayer=null;
|
||||||
}
|
}
|
||||||
pugPlayer = flvjs.createPlayer(config);
|
pugPlayer = mpegts.createPlayer(config);
|
||||||
pugPlayer.attachMediaElement(video);
|
pugPlayer.attachMediaElement(video);
|
||||||
pugPlayer.load();
|
pugPlayer.load();
|
||||||
pugPlayer.on(flvjs.Events.METADATA_ARRIVED, function(res){
|
pugPlayer.on(mpegts.Events.METADATA_ARRIVED, function(res){
|
||||||
videoHandler.loadedMetaData();
|
videoHandler.loadedMetaData();
|
||||||
});
|
});
|
||||||
pugPlayer.on(flvjs.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
pugPlayer.on(mpegts.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
||||||
CT.error={code:errorInfo['code'],message:errorInfo['msg']};
|
CT.error={code:errorInfo['code'],message:errorInfo['msg']};
|
||||||
eventTarget('error',CT.error);//注册监听error
|
eventTarget('error',CT.error);//注册监听error
|
||||||
showError();
|
showError();
|
||||||
@@ -670,7 +679,7 @@ var language = languageDefault;
|
|||||||
mpegtsPlayer=function (url){
|
mpegtsPlayer=function (url){
|
||||||
var path=getPath('mpegts.js')+'mpegts.js';
|
var path=getPath('mpegts.js')+'mpegts.js';
|
||||||
loadJs(path,function(){
|
loadJs(path,function(){
|
||||||
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.getFeatureList().mseLivePlayback) {
|
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||||
var config={
|
var config={
|
||||||
type: 'mse',
|
type: 'mse',
|
||||||
url: url
|
url: url
|
||||||
@@ -703,6 +712,29 @@ var language = languageDefault;
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
|
* getPlugByUrl
|
||||||
|
* 功能:根据视频地址的扩展名自动识别需要使用的插件
|
||||||
|
* 识别不到时返回''(维持原生播放路径),URL支持携带query/hash参数
|
||||||
|
*/
|
||||||
|
getPlugByUrl=function(url){
|
||||||
|
if(valType(url)!='string'){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var purePath=url.split('?')[0].split('#')[0];
|
||||||
|
var ext=purePath.substring(purePath.lastIndexOf('.')+1).toLowerCase();
|
||||||
|
switch(ext){
|
||||||
|
case 'm3u8':
|
||||||
|
return 'hls.js';
|
||||||
|
case 'flv':
|
||||||
|
return 'mpegts.js';
|
||||||
|
case 'ts':
|
||||||
|
case 'm2ts':
|
||||||
|
return 'mpegts.js';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
* canPlay
|
* canPlay
|
||||||
* 功能:判断是否能支持相关视频格式
|
* 功能:判断是否能支持相关视频格式
|
||||||
@@ -742,7 +774,9 @@ var language = languageDefault;
|
|||||||
return sup;
|
return sup;
|
||||||
};
|
};
|
||||||
var getExtension=function(filepath) {
|
var getExtension=function(filepath) {
|
||||||
return filepath.replace(/.+\./, '');
|
//先剥离query与hash,避免 xxx.m3u8?token=... 之类地址无法识别扩展名
|
||||||
|
var purePath=filepath.split('?')[0].split('#')[0];
|
||||||
|
return purePath.replace(/.+\./, '');
|
||||||
};
|
};
|
||||||
if(isUndefined(num)){
|
if(isUndefined(num)){
|
||||||
if(valType(vStr)=='string'){
|
if(valType(vStr)=='string'){
|
||||||
@@ -1225,6 +1259,24 @@ var language = languageDefault;
|
|||||||
CM.doubleClick(player.fullOrExit);//监听视频双击
|
CM.doubleClick(player.fullOrExit);//监听视频双击
|
||||||
$(document).addListener('keydown',videoHandler.keydown);//监听键盘按键
|
$(document).addListener('keydown',videoHandler.keydown);//监听键盘按键
|
||||||
addListener(window, 'resize', videoHandler.resize);//监听窗口尺寸变化
|
addListener(window, 'resize', videoHandler.resize);//监听窗口尺寸变化
|
||||||
|
/*
|
||||||
|
* 监听video元素原生全屏的进出(iOS Safari),同步内部全屏状态
|
||||||
|
*/
|
||||||
|
video.addListener('webkitbeginfullscreen',function(){
|
||||||
|
videoNativeFull=true;
|
||||||
|
videoHandler.resize();
|
||||||
|
});
|
||||||
|
video.addListener('webkitendfullscreen',function(){
|
||||||
|
videoNativeFull=false;
|
||||||
|
videoHandler.resize();
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* 监听标准全屏状态变化(桌面端),保持全屏状态准确
|
||||||
|
*/
|
||||||
|
addListener(document,'fullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'webkitfullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'mozfullscreenchange',videoHandler.resize);
|
||||||
|
addListener(document,'MSFullscreenChange',videoHandler.resize);
|
||||||
if(!isUndefined(vars['smallWindows'])){
|
if(!isUndefined(vars['smallWindows'])){
|
||||||
if(valType(vars['smallWindows'])=='boolean' && vars['smallWindows']){
|
if(valType(vars['smallWindows'])=='boolean' && vars['smallWindows']){
|
||||||
addListener(window, 'scroll', windowScroll);//监听窗口滚动
|
addListener(window, 'scroll', windowScroll);//监听窗口滚动
|
||||||
@@ -1769,7 +1821,7 @@ var language = languageDefault;
|
|||||||
replaceInformation('volume',parseInt(this.volume*100));
|
replaceInformation('volume',parseInt(this.volume*100));
|
||||||
},
|
},
|
||||||
resize:function(){
|
resize:function(){
|
||||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||||
if(fullState) {
|
if(fullState) {
|
||||||
C['bar']['fullAndExit']['exitFull'].show();
|
C['bar']['fullAndExit']['exitFull'].show();
|
||||||
C['bar']['fullAndExit']['full'].hide();
|
C['bar']['fullAndExit']['full'].hide();
|
||||||
@@ -2892,6 +2944,11 @@ var language = languageDefault;
|
|||||||
* 功能: 退出全屏
|
* 功能: 退出全屏
|
||||||
*/
|
*/
|
||||||
exitFull:function(){
|
exitFull:function(){
|
||||||
|
//iOS Safari上video处于原生全屏时,通过video.webkitExitFullscreen退出
|
||||||
|
if(videoNativeFull && !isUndefined(video.webkitExitFullscreen)){
|
||||||
|
video.webkitExitFullscreen();
|
||||||
|
return player;
|
||||||
|
}
|
||||||
var exitFullFun = document.exitFullscreen || //W3C
|
var exitFullFun = document.exitFullscreen || //W3C
|
||||||
document.mozCancelFullScreen || //FireFox
|
document.mozCancelFullScreen || //FireFox
|
||||||
document.webkitExitFullscreen || //Chrome等
|
document.webkitExitFullscreen || //Chrome等
|
||||||
@@ -2913,7 +2970,7 @@ var language = languageDefault;
|
|||||||
* 功能:在全屏和退出全屏之间进行切换
|
* 功能:在全屏和退出全屏之间进行切换
|
||||||
*/
|
*/
|
||||||
fullOrExit:function(){
|
fullOrExit:function(){
|
||||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||||
if(fullState){
|
if(fullState){
|
||||||
player.exitFull();
|
player.exitFull();
|
||||||
}
|
}
|
||||||
@@ -4928,6 +4985,16 @@ var language = languageDefault;
|
|||||||
bg.mousemove(bgMouseMove);
|
bg.mousemove(bgMouseMove);
|
||||||
bg.mouseout(bgMouseOut);
|
bg.mouseout(bgMouseOut);
|
||||||
mouseLine.mouseover(clearTime);
|
mouseLine.mouseover(clearTime);
|
||||||
|
/*
|
||||||
|
* 移动端:支持在进度条上按住拖动seek,复用bgMouseDown,preventDefault防止页面滚动
|
||||||
|
*/
|
||||||
|
var bgTouchSeek=function(e){
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
bgMouseDown(e);
|
||||||
|
};
|
||||||
|
bg.touchstart(bgTouchSeek);
|
||||||
|
bg.touchmove(bgTouchSeek);
|
||||||
|
bg.touchend(bgTouchSeek);
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* volumeDragY
|
* volumeDragY
|
||||||
@@ -5003,6 +5070,16 @@ var language = languageDefault;
|
|||||||
player.volume(vol);
|
player.volume(vol);
|
||||||
};
|
};
|
||||||
bg.mousedown(bgMouseDown);
|
bg.mousedown(bgMouseDown);
|
||||||
|
/*
|
||||||
|
* 移动端:支持在音量条上按住拖动调节音量,复用bgMouseDown,preventDefault防止页面滚动
|
||||||
|
*/
|
||||||
|
var bgTouchVolume=function(e){
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
bgMouseDown(e);
|
||||||
|
};
|
||||||
|
bg.touchstart(bgTouchVolume);
|
||||||
|
bg.touchmove(bgTouchVolume);
|
||||||
|
bg.touchend(bgTouchVolume);
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* changeProgress
|
* changeProgress
|
||||||
@@ -6315,11 +6392,24 @@ var language = languageDefault;
|
|||||||
}
|
}
|
||||||
var x=eve.clientX + (document.documentElement.scrollLeft || $('body').scrollLeft),
|
var x=eve.clientX + (document.documentElement.scrollLeft || $('body').scrollLeft),
|
||||||
y=eve.clientY + (document.documentElement.scrollTop || $('body').scrollTop);
|
y=eve.clientY + (document.documentElement.scrollTop || $('body').scrollTop);
|
||||||
if(isUndefined(x.toString()) || x.toString()=='NaN'){
|
if(isUndefined(x.toString()) || x.toString()=='NaN' || isUndefined(y.toString()) || y.toString()=='NaN'){
|
||||||
x=eve.touches[0].clientX
|
//触摸事件:touchstart/touchmove取touches;touchend时touches为空,取changedTouches兜底
|
||||||
|
var touch=null;
|
||||||
|
if(!isUndefined(eve.touches) && eve.touches.length>0){
|
||||||
|
touch=eve.touches[0];
|
||||||
|
}
|
||||||
|
else if(!isUndefined(eve.changedTouches) && eve.changedTouches.length>0){
|
||||||
|
touch=eve.changedTouches[0];
|
||||||
|
}
|
||||||
|
if(touch){
|
||||||
|
//与mouse路径对齐,补上页面滚动偏移
|
||||||
|
x=touch.clientX + (window.pageXOffset || document.documentElement.scrollLeft || 0);
|
||||||
|
y=touch.clientY + (window.pageYOffset || document.documentElement.scrollTop || 0);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
x=0;
|
||||||
|
y=0;
|
||||||
}
|
}
|
||||||
if(isUndefined(y.toString()) || y.toString()=='NaN'){
|
|
||||||
y=eve.touches[0].clientY
|
|
||||||
}
|
}
|
||||||
if(x.toString()=='NaN'){
|
if(x.toString()=='NaN'){
|
||||||
x=0;
|
x=0;
|
||||||
|
|||||||
+6
-2
@@ -173,11 +173,15 @@ import { addListener } from './dom.js';
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* getApp
|
* getApp
|
||||||
* 功能,获取平台类型是否是iphone
|
* 功能,获取平台类型是否是iphone(含ipod/ipad,统一返回'iphone'以保持现有分支兼容)
|
||||||
*/
|
*/
|
||||||
function getApp(){
|
function getApp(){
|
||||||
var u = navigator.userAgent.toLowerCase();
|
var u = navigator.userAgent.toLowerCase();
|
||||||
if(u.indexOf('iphone')>-1){
|
if(u.indexOf('iphone')>-1 || u.indexOf('ipod')>-1 || u.indexOf('ipad')>-1){
|
||||||
|
return 'iphone';
|
||||||
|
}
|
||||||
|
//iPadOS 13+默认使用桌面UA(Macintosh),通过MacIntel平台+触屏点数判断
|
||||||
|
if(u.indexOf('macintosh')>-1 && navigator.platform=='MacIntel' && navigator.maxTouchPoints>1){
|
||||||
return 'iphone';
|
return 'iphone';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user