├── .gitignore ├── README.md ├── babel.config.js ├── docs ├── .vitepress │ ├── config.js │ └── theme │ │ ├── index.js │ │ ├── register-components.js │ │ └── styles │ │ └── index.css ├── components │ └── index.vue ├── guide │ ├── changelog.md │ └── install.md └── index.md ├── index.html ├── lib ├── components │ ├── d-contextmenu.vue │ ├── d-icon.vue │ ├── d-loading.vue │ ├── d-player-top.vue │ ├── d-slider.vue │ ├── d-status.vue │ ├── d-switch.vue │ └── index.js ├── index.js ├── style │ ├── animate.less │ ├── base.less │ ├── iconfont.css │ ├── iconfont.woff2 │ ├── reset.less │ ├── transition.less │ └── vPlayer.less ├── utils │ ├── dom.ts │ └── util.ts └── video-play │ ├── main.vue │ └── plugins │ └── index.ts ├── package.json ├── scripts ├── gh-pages.sh ├── publish copy.js └── publish.js ├── src ├── App.vue ├── main.ts ├── shims-vue.d.ts ├── utils │ └── index.js └── vite-env.d.ts ├── tsconfig.json ├── vite.config.ts ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | [![Version](https://img.shields.io/npm/dt/vue3-video-play.svg?style=flat-square)](https://www.npmjs.com/package/vue3-video-play) 10 | [![Downloads](https://img.shields.io/npm/v/vue3-video-play.svg?style=flat-square)](https://www.npmjs.com/package/vue3-video-play) 11 | [![GitHub stars](https://img.shields.io/github/stars/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/stargazers) 12 | [![GitHub issues](https://img.shields.io/github/issues/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/issues) 13 | [![GitHub forks](https://img.shields.io/github/forks/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/network) 14 | [![GitHub last commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play) 15 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play) 16 | 17 | [![NPM](https://nodei.co/npm/vue3-video-play.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/vue3-video-play) 18 | 19 | **必须使用 vue@3.2.2及以上版本** 20 | 21 | ### Vue3-video-play 22 | 23 | 适用于 Vue3 的 hls.js 播放器组件 | 并且支持 MP4/WebM/Ogg 格式 24 | 配置强大,UI 还算好看 25 | 26 | ## 功能一览 27 | 28 | 1. 支持快捷键操作 29 | 2. 支持倍速播放设置 30 | 3. 支持镜像画面设置 31 | 4. 支持关灯模式设置 32 | 5. 支持画中画模式播放 33 | 6. 支持全屏/网页全屏播放 34 | 7. 支持从固定时间开始播放 35 | 8. 支持移动端,移动端会自动调用自带视频播放器 36 | 9. 支持 hls 视频流播放,支持直播 37 | 10. hls 播放支持清晰度切换 38 | 39 | # 主页示例 40 | 41 | [https://codelife.cc/vue3-video-play/](https://codelife.cc/vue3-video-play/) 42 | 43 | ## 近期更新 v1.3.3 🎉 44 | 45 | - 修复: 右键事件错误 46 | 47 | # 使用指南 48 | 49 | ## 安装 50 | 51 | npm 安装: 52 | 53 | ```bash 54 | npm i vue3-video-play --save 55 | ``` 56 | 57 | yarn 安装: 58 | 59 | ```bash 60 | yarn add vue3-video-play --save 61 | ``` 62 | 63 | ## 开始使用 64 | 65 | #### 全局使用 66 | 67 | ```js 68 | import { createApp } from "vue"; 69 | import App from "./App.vue"; 70 | let app = createApp(App); 71 | 72 | import vue3videoPlay from "vue3-video-play"; // 引入组件 73 | import "vue3-video-play/dist/style.css"; // 引入css 74 | app.use(vue3videoPlay); 75 | 76 | app.mount("#app"); 77 | ``` 78 | 79 | #### 组件内使用 80 | 81 | ```js 82 | // require style 83 | import "vue3-video-play/dist/style.css"; 84 | import { videoPlay } from "vue3-video-play"; 85 | export default { 86 | components: { 87 | videoPlay, 88 | }, 89 | }; 90 | ``` 91 | 92 | ## 基本示例 93 | 94 | 提供了丰富了配置功能 95 | :::demo 自定义配置 比如自定义 poster。 96 | 97 | ```vue 98 | 106 | 107 | 136 | 137 | 138 | ``` 139 | 140 | ::: 141 | 142 | 可以通过`props`的`speed`开启或关闭进度条功能, 并且通过 `currentTime`属性控制从 60 秒开始播放 143 | 144 | :::demo 通过`speed`关闭进度条拖动功能。 并且通过 `currentTime`属性控制从 60 秒开始播放 145 | 146 | ```vue 147 | 155 | 156 | 169 | 170 | 171 | ``` 172 | 173 | ::: 174 | 175 | 还可以通过`props`的`control`属性 来控制是否显示控制器 176 | :::demo 通过`control` 来控制是否显示控制器 177 | 178 | ```vue 179 | 187 | 188 | 200 | 201 | 202 | ``` 203 | 204 | ::: 205 | 206 | ## 事件示例 207 | 208 | :::demo `vue3-video-play` 支持原生`video`所有事件。 209 | 210 | ```vue 211 | 225 | 226 | 247 | 248 | 249 | ``` 250 | 251 | ::: 252 | 253 | ## Hls m3u8 视频/直播 254 | 255 | :::demo `vue3-video-play` 支持 m3u8(hls)播放 256 | 257 | ```vue 258 | 269 | 276 | 277 | 278 | ``` 279 | 280 | ::: 281 | 282 | ## Props 283 | 284 | vue3-video-play 支持 video 原生所有 Attributes [video 原生属性](https://segmentfault.com/a/1190000008053507) 使用方式和 props 属性使用一致 285 | 286 | | 名称 | 说明 | 类型 | 可选值 | 默认值 | 287 | | ------------- | :-------------------: | :-----: | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | 288 | | width | 播放器宽度 | string | - | 800px | 289 | | height | 播放器高度 | string | - | 450px | 290 | | color | 播放器主色调 | string | - | #409eff | 291 | | src | 视频资源 | string | - | - | 292 | | title | 视频名称 | string | - | - | 293 | | type | 视频类型 | string | - | video/mp4|m3u8 | 294 | | poster | 视频封面 | string | - | 视频第一帧 | 295 | | webFullScreen | 网页全屏 | boolean | - | false | 296 | | speed | 是否支持快进快退 | boolean | - | true | 297 | | currentTime | 跳转到固定播放时间(s) | number | - | 0 | 298 | | playsinline | ios 点击屏幕是否全屏 | boolean | - | false | 299 | | muted | 静音 | boolean | - | false | 300 | | speedRate | 倍速配置 | array | - | ["2.0", "1.0", "1.5", "1.25", "0.75", "0.5"] | 301 | | autoPlay | 自动播放 | boolean | - | false,为 true 时会自动静音 | 302 | | loop | 循环播放 | boolean | - | false | 303 | | mirror | 镜像画面 | boolean | - | false | 304 | | ligthOff | 关灯模式 | boolean | - | false | 305 | | volume | 默认音量 | number | 0-1 | 0.3 | 306 | | control | 是否显示控制器 | boolean | - | true | 307 | | controlBtns | 控制器显示的按钮 | array | ['audioTrack', 'quality', 'speedRate', 'volume', 'setting', 'pip', 'pageFullScreen', 'fullScreen'] | ['audioTrack', 'quality', 'speedRate', 'volume', 'setting', 'pip', 'pageFullScreen', 'fullScreen'] | 308 | | preload | 预加载 | string | meta/auto/none | auto | 309 | 310 | ### `props`属性 `controlBtns` 按钮说明 311 | 312 | | 名称 | 说明 | 313 | | -------------- | :--------------: | 314 | | audioTrack | 音轨切换按钮 | 315 | | quality | 视频质量切换按钮 | 316 | | speedRate | 速率切换按钮 | 317 | | volume | 音量 | 318 | | setting | 设置 | 319 | | pip | 画中画按钮 | 320 | | pageFullScreen | 网页全屏按钮 | 321 | | fullScreen | 全屏按钮 | 322 | 323 | ## Events 324 | 325 | vue3-video-play 支持 video 原生所有事件 [video 默认事件](https://segmentfault.com/a/1190000008053507) 326 | 327 | | 事件名称 | 说明 | 回调 | 328 | | -------------- | ------------------ | ----- | 329 | | mirrorChange | 镜像翻转事件 | val | 330 | | loopChange | 循环播放开关事件 | val | 331 | | lightOffChange | 关灯模式事件 | val | 332 | | loadstart | 客户端开始请求数据 | event | 333 | | progress | 客户端正在请求数据 | event | 334 | | error | 请求数据时遇到错误 | event | 335 | | stalled | 网速失速 | event | 336 | | play | 开始播放时触发 | event | 337 | | pause | 暂停时触发 | event | 338 | | loadedmetadata | 成功获取资源长度 | event | 339 | | loadeddata | 缓冲中 | event | 340 | | waiting | 等待数据,并非错误 | event | 341 | | playing | 开始回放 | event | 342 | | canplay | 暂停状态下可以播放 | event | 343 | | canplaythrough | 可以持续播放 | event | 344 | | timeupdate | 更新播放时间 | event | 345 | | ended | 播放结束 | event | 346 | | ratechange | 播放速率改变 | event | 347 | | durationchange | 资源长度改变 | event | 348 | | volumechange | 音量改变 | event | 349 | 350 | ## 快捷键说明 351 | 352 | 支持快捷键操作 353 | | 键名 | 说明 | 354 | | ---------- | ----------------------------- | 355 | | Space | 暂停/播放 | 356 | | 方向右键 → | 单次快进 10s,长按 5 倍速播放 | 357 | | 方向左键 ← | 快退 10s | 358 | | 方向上键 ↑ | 音量+10% | 359 | | 方向下键 ↓ | 音量-10% | 360 | | Esc | 退出全屏/退出网页全屏 | 361 | | F | 全屏/退出全屏 | 362 | 363 | # Author 364 | 365 | [xdlumia](https://codelife.cc) 366 | 367 | # 点个 start 368 | 369 | [vue3-video-play](https://github.com/xdlumia/vue3-video-play) 370 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-27 22:49:49 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-27 22:57:16 6 | * @Description: file content 7 | */ 8 | module.exports = { 9 | presets: [ 10 | '@vue/app' 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-proposal-optional-chaining" 14 | ] 15 | } -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-19 18:56:59 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-10-10 22:37:33 6 | * @Description: file content 7 | */ 8 | module.exports = { 9 | title: "Vue3VideoPlay", // 网站标题 10 | description: 'Vue3VideoPlay 基于vue3编写的视频播放器', //网站描述 11 | keyword: 'videoPlayer,vue3Video, hlsjs, m3u8视频流播放', //网站描述 12 | // lang: 'en-US', //语言 13 | base: '/vue3-video-play/', 14 | repo: 'vuejs/vitepress', 15 | head: [ 16 | // 改变title的图标 17 | // [ 18 | // 'link', 19 | // { 20 | // rel: 'icon', 21 | // href: '/img/linktolink.png', //图片放在public文件夹下 22 | // }, 23 | // ], 24 | ], 25 | markdown: { 26 | 27 | config: (md) => { 28 | const { 29 | demoBlockPlugin 30 | } = require('vitepress-theme-demoblock') 31 | md.use(demoBlockPlugin) 32 | } 33 | }, 34 | // 主题配置 35 | themeConfig: { 36 | // 头部导航 37 | nav: [{ 38 | text: '首页', 39 | link: '/' 40 | }, { 41 | text: '使用指南', 42 | link: '/guide/install' 43 | }, { 44 | text: 'github', 45 | link: 'https://github.com/xdlumia/vue3-video-play' 46 | }], 47 | // 侧边导航 48 | sidebar: [{ 49 | text: '更新日志', 50 | link: '/guide/changelog' 51 | }, { 52 | text: '使用指南', 53 | link: '/guide/install' 54 | }], 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-21 15:12:41 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-31 14:57:41 6 | * @Description: file content 7 | */ 8 | import theme from 'vitepress/dist/client/theme-default' 9 | import 'vitepress-theme-demoblock/theme/styles/index.css' 10 | import './styles/index.css' 11 | import { 12 | registerComponents 13 | } from './register-components' 14 | 15 | import 'vue3-video-play/dist/style.css' 16 | const isClient = typeof window == 'object' 17 | export default { 18 | ...theme, 19 | async enhanceApp({ 20 | app, 21 | router, 22 | siteData 23 | }) { 24 | if (isClient) { 25 | await import('vue3-video-play').then((m) => { 26 | app.use(m.default) 27 | }) 28 | // await import('../../../lib/index.js').then((m) => { 29 | // app.use(m.default) 30 | // }) 31 | } 32 | // app is the Vue 3 app instance from createApp() 33 | // router is VitePress' custom router (see `lib/app/router.js`) 34 | // siteData is a ref of current site-level metadata. 35 | 36 | registerComponents(app) 37 | } 38 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/register-components.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-21 15:12:41 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-25 16:07:32 6 | * @Description: file content 7 | */ 8 | import Demo from 'vitepress-theme-demoblock/components/Demo.vue' 9 | import DemoBlock from 'vitepress-theme-demoblock/components/DemoBlock.vue' 10 | // import { 11 | // videoPlay 12 | // } from 'vue3-video-play' 13 | 14 | export function registerComponents(app) { 15 | app.component('Demo', Demo) 16 | app.component('DemoBlock', DemoBlock) 17 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/styles/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --c-brand: #646cff; 3 | --c-brand-light: #747bff; 4 | } 5 | 6 | .demo-block .xl-button { 7 | margin: 0 10px 10px 0; 8 | } 9 | -------------------------------------------------------------------------------- /docs/components/index.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-21 19:20:46 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-10-11 12:59:55 6 | * @Description: file content 7 | */ 8 | 78 | 79 | 93 | 94 | -------------------------------------------------------------------------------- /docs/guide/changelog.md: -------------------------------------------------------------------------------- 1 | 8 | 11 | # 更新日志 12 | ### 1.3.1-beta.2 13 | *2021-09-1* 14 | - 修复: 工具栏播放/暂停按钮构建后不能点击的问题 15 | - 修复: 缓冲动画构建后动画丢失的问题 16 | ### 1.3.1-beta.1 17 | *2021-09-1* 18 | - 新增: props参数增加`controlBtns`属性,自定义控制器按钮显示 19 | - 修复: m3u8切换视频源不生效的bug 20 | - 修复: 设置poster不生效的bug(#5) 21 | - 优化: 视频加载后中间增加播放按钮 22 | ### 1.3.0-rc.3 23 | *2021-08-31* 24 | 25 | - 新增: 支持hls视频/直播 26 | - 新增: 新增画质切换,需视频支持 27 | - 新增: 新增画音视切换,需视频支持 28 | - 新增: props参数增加`currentTime`属性,可跳转到固定时间播放 29 | - 新增: props参数增加`type`属性,视频格式 30 | 31 | ### 1.2.52 32 | *2021-08-27* 33 | 34 | - 优化: 优化重新播放按钮样式偏移问题 35 | 36 | ### 1.2.51 37 | *2021-08-25* 38 | 39 | - 新增: `mirrorChange` `loopChange` `lightOffChange` 事件 40 | - 新增: 倍速播放默认配置,增加`0.5`倍速播放 41 | - 新增: 资源播放失败错误状态 42 | - 新增: 非循环播放状态下播放结束增加重新播放按钮 43 | - 新增: 增加空格快捷键 `播放/暂停` 的操作 44 | - 修复: 关灯模式不能覆盖菜单栏区域 45 | - 优化: svg图标更换成字体图标,总体减少8KB 46 | - 优化: 如果音量为0关闭静音按钮 音量设置为5 47 | 48 | 49 | ### 1.2.4 50 | *2021-08-24* 51 | 52 | - Refactors 重构进度条拖动和音量拖动功能,精简代码 53 | - Chore 处理鼠标移动到进度条上距左或者距右时间显示显示不完整的问题 54 | ### 1.2.3 55 | *2021-08-23* 56 | 57 | - Chore 暂停状态下视频中央增加播放按钮 58 | 59 | ### 1.2.2 60 | *2021-08-23* 61 | 62 | - Add `props` 新增 `speed` 属性,开启或者关闭快进快退,默认true ([#2](https://github.com/xdlumia/vue3-video-play/issues/2)) 63 | - Fix 方向键快进快退的时候会移动页面的问题 64 | - Chore 双击视频全屏/关闭全屏 65 | - Chore 修改`设置`按钮鼠标`hover`效果 66 | 67 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | 8 | [![Version](https://img.shields.io/npm/dt/vue3-video-play.svg?style=flat-square)](https://www.npmjs.com/package/vue3-video-play) 9 | [![Downloads](https://img.shields.io/npm/v/vue3-video-play.svg?style=flat-square)](https://www.npmjs.com/package/vue3-video-play) 10 | [![GitHub stars](https://img.shields.io/github/stars/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/stargazers) 11 | [![GitHub issues](https://img.shields.io/github/issues/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/issues) 12 | [![GitHub forks](https://img.shields.io/github/forks/xdlumia/vue3-video-play.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play/network) 13 | [![GitHub last commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play) 14 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](https://github.com/xdlumia/vue3-video-play) 15 | 16 | [![NPM](https://nodei.co/npm/vue3-video-play.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/vue3-video-play) 17 | 18 | 19 | **必须使用 vue@3.2.2及以上版本** 20 | 21 | ### Vue3-video-play 22 | 23 | 适用于 Vue3 的 hls.js 播放器组件 | 并且支持 MP4/WebM/Ogg 格式 24 | 配置强大,UI 还算好看 25 | 26 | ## 功能一览 27 | 1. 支持快捷键操作 28 | 2. 支持倍速播放设置 29 | 3. 支持镜像画面设置 30 | 4. 支持关灯模式设置 31 | 5. 支持画中画模式播放 32 | 6. 支持全屏/网页全屏播放 33 | 6. 支持从固定时间开始播放 34 | 8. 支持移动端,移动端会自动调用自带视频播放器 35 | 9. 支持hls视频流播放,支持直播 36 | 10. hls播放支持清晰度切换 37 | # 主页示例 38 | 39 | [https://codelife.cc/vue3-video-play/](https://codelife.cc/vue3-video-play/) 40 | 41 | 42 | 43 | ## 近期更新 v1.3.1-beta.2 🎉 44 | - 新增: 支持hls视频流播放 45 | - 新增: 新增画质切换,需视频流支持 46 | - 新增: 新增画音视切换,需视频流支持 47 | - 新增: props参数增加`currentTime`属性,可跳转到固定时间播放 48 | - 新增: props参数增加`type`属性,视频格式 49 | - 新增: props参数增加`controlBtns`属性,自定义控制器按钮显示 50 | - 新增: 右键菜单功能,右键菜单包涵,视频滤镜调节、快捷键说明、复制当前视频网址 51 | - 新增: `mirrorChange` `loopChange` `lightOffChange` 事件 52 | - 新增: 增加空格快捷键 `播放/暂停` 的操作 53 | - 优化: 如果音量为 0 关闭静音按钮 音量设置为 5 54 | # 使用指南 55 | 56 | ## 安装 57 | npm安装: 58 | ``` bash 59 | npm i vue3-video-play --save 60 | ``` 61 | yarn安装: 62 | ``` bash 63 | yarn add vue3-video-play --save 64 | ``` 65 | 66 | ## 开始使用 67 | 68 | #### 全局使用 69 | 70 | ``` js 71 | import { createApp } from 'vue' 72 | import App from './App.vue' 73 | let app = createApp(App) 74 | 75 | import vue3videoPlay from 'vue3-video-play' // 引入组件 76 | import 'vue3-video-play/dist/style.css' // 引入css 77 | app.use(vue3videoPlay) 78 | 79 | app.mount('#app') 80 | ``` 81 | 82 | #### 组件内使用 83 | 84 | ```js 85 | // require style 86 | import 'vue3-video-play/dist/style.css' 87 | import { videoPlay } from 'vue-video-play' 88 | export default { 89 | components: { 90 | videoPlay 91 | } 92 | } 93 | ``` 94 | 95 | 96 | ## 基本示例 97 | 提供了丰富了配置功能 98 | :::demo 自定义配置 比如自定义poster。 99 | 100 | ```vue 101 | 106 | 107 | 127 | 128 | 130 | ``` 131 | 132 | ::: 133 | 134 | 可以通过`props`的`speed`开启或关闭进度条功能, 并且通过 `currentTime`属性控制从60秒开始播放 135 | 136 | :::demo 通过`speed`关闭进度条拖动功能。 并且通过 `currentTime`属性控制从60秒开始播放 137 | 138 | ```vue 139 | 144 | 145 | 158 | 159 | 161 | ``` 162 | 163 | ::: 164 | 165 | 166 | 167 | 还可以通过`props`的`control`属性 来控制是否显示控制器 168 | :::demo 通过`control` 来控制是否显示控制器 169 | ```vue 170 | 175 | 176 | 188 | 189 | 191 | 192 | ``` 193 | ::: 194 | 195 | 196 | 197 | ## 事件示例 198 | :::demo `vue3-video-play` 支持原生`video`所有事件。 199 | 200 | ```vue 201 | 215 | 216 | 217 | 238 | 239 | 241 | 242 | ``` 243 | 244 | ::: 245 | 246 | 247 | 248 | ## Hls m3u8视频/直播 249 | :::demo `vue3-video-play` 支持m3u8(hls)播放 250 | ```vue 251 | 263 | 270 | 271 | 273 | 274 | ``` 275 | ::: 276 | 277 | 278 | 279 | ## Props 280 | vue3-video-play 支持video原生所有Attributes [video原生属性](https://segmentfault.com/a/1190000008053507) 使用方式和props属性使用一致 281 | 282 | | 名称 | 说明 | 类型 | 可选值 | 默认值 | 283 | | ------------- | :-------------------: | :-----: | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | 284 | | width | 播放器宽度 | string | - | 800px | 285 | | height | 播放器高度 | string | - | 450px | 286 | | color | 播放器主色调 | string | - | #409eff | 287 | | src | 视频资源 | string | - | - | 288 | | title | 视频名称 | string | - | - | 289 | | type | 视频类型 | string | - | video/mp4 | 290 | | poster | 视频封面 | string | - | 视频第一帧 | 291 | | webFullScreen | 网页全屏 | boolean | - | false | 292 | | speed | 是否支持快进快退 | boolean | - | true | 293 | | currentTime | 跳转到固定播放时间(s) | number | - | 0 | 294 | | playsinline | ios点击屏幕是否全屏 | boolean | - | false | 295 | | muted | 静音 | boolean | - | false | 296 | | speedRate | 倍速配置 | array | - | ["2.0", "1.0", "1.5", "1.25", "0.75", "0.5"] | 297 | | autoPlay | 自动播放 | boolean | - | false,为true时会自动静音 | 298 | | loop | 循环播放 | boolean | - | false | 299 | | mirror | 镜像画面 | boolean | - | false | 300 | | ligthOff | 关灯模式 | boolean | - | false | 301 | | volume | 默认音量 | number | 0-1 | 0.3 | 302 | | control | 是否显示控制器 | boolean | - | true | 303 | | controlBtns | 控制器显示的按钮 | array | ['audioTrack', 'quality', 'speedRate', 'volume', 'setting', 'pip', 'pageFullScreen', 'fullScreen'] | ['audioTrack', 'quality', 'speedRate', 'volume', 'setting', 'pip', 'pageFullScreen', 'fullScreen'] | 304 | | preload | 预加载 | string | meta/auto/none | auto | 305 | 306 | ### `props`属性 `controlBtns` 按钮说明 307 | | 名称 | 说明 | 308 | | -------------- | :--------------: | 309 | | audioTrack | 音轨切换按钮 | 310 | | quality | 视频质量切换按钮 | 311 | | speedRate | 速率切换按钮 | 312 | | volume | 音量 | 313 | | setting | 设置 | 314 | | pip | 画中画按钮 | 315 | | pageFullScreen | 网页全屏按钮 | 316 | | fullScreen | 全屏按钮 | 317 | 318 | ## Events 319 | vue3-video-play支持video原生所有事件 [video默认事件](https://segmentfault.com/a/1190000008053507) 320 | 321 | | 事件名称 | 说明 | 回调 | 322 | | -------------- | ------------------ | ----- | 323 | | mirrorChange | 镜像翻转事件 | val | 324 | | loopChange | 循环播放开关事件 | val | 325 | | lightOffChange | 关灯模式事件 | val | 326 | | loadstart | 客户端开始请求数据 | event | 327 | | progress | 客户端正在请求数据 | event | 328 | | error | 请求数据时遇到错误 | event | 329 | | stalled | 网速失速 | event | 330 | | play | 开始播放时触发 | event | 331 | | pause | 暂停时触发 | event | 332 | | loadedmetadata | 成功获取资源长度 | event | 333 | | loadeddata | 缓冲中 | event | 334 | | waiting | 等待数据,并非错误 | event | 335 | | playing | 开始回放 | event | 336 | | canplay | 暂停状态下可以播放 | event | 337 | | canplaythrough | 可以持续播放 | event | 338 | | timeupdate | 更新播放时间 | event | 339 | | ended | 播放结束 | event | 340 | | ratechange | 播放速率改变 | event | 341 | | durationchange | 资源长度改变 | event | 342 | | volumechange | 音量改变 | event | 343 | 344 | ## 快捷键说明 345 | 支持快捷键操作 346 | | 键名 | 说明 | 347 | | ---------- | ----------------------------- | 348 | | Space | 暂停/播放 | 349 | | 方向右键 → | 单次快进 10s,长按 5 倍速播放 | 350 | | 方向左键 ← | 快退 10s | 351 | | 方向上键 ↑ | 音量+10% | 352 | | 方向下键 ↓ | 音量-10% | 353 | | Esc | 退出全屏/退出网页全屏 | 354 | | F | 全屏/退出全屏 | 355 | # Author 356 | 357 | [xdlumia](https://codelife.cc) 358 | 359 | # 点个start 360 | 361 | [vue3-video-play](https://github.com/xdlumia/vue3-video-play) 362 | 363 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | --- 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Vite App 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/components/d-contextmenu.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-26 12:13:47 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 14:44:07 6 | * @Description: file content 7 | */ 8 | 84 | 85 | 204 | 205 | -------------------------------------------------------------------------------- /lib/components/d-icon.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-10-27 10:31:35 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-28 07:14:47 6 | * @Description: file content 7 | */ 8 | 14 | 20 | 33 | 41 | -------------------------------------------------------------------------------- /lib/components/d-loading.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-20 11:00:41 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-09-01 18:08:30 6 | * @Description: file content 7 | */ 8 | 40 | 41 | 64 | 65 | 104 | -------------------------------------------------------------------------------- /lib/components/d-player-top.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-19 16:59:13 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-28 07:14:57 6 | * @Description: file content 7 | */ 8 | 14 | 15 | 49 | 50 | -------------------------------------------------------------------------------- /lib/components/d-slider.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-23 21:12:57 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 15:33:06 6 | * @Description: file content 7 | */ 8 | 32 | 37 | 173 | -------------------------------------------------------------------------------- /lib/components/d-status.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-20 13:52:52 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 15:28:50 6 | * @Description: file content 7 | */ 8 | 28 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /lib/components/d-switch.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-20 09:34:45 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-28 07:15:07 6 | * @Description: file content 7 | */ 8 | 22 | 23 | 62 | -------------------------------------------------------------------------------- /lib/components/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-03-17 17:16:37 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2020-10-29 10:43:30 6 | * @Description: 收集公共组件 公共组件入口必须是index.js命名 并且必须正确命名name 7 | */ 8 | 9 | const files = require.context('./', true, /\index.js$/) 10 | 11 | export default files.keys().filter(v => v != './index.js').reduce((arr, key) => { 12 | let comp = files(key).default 13 | return [...arr, comp] 14 | }, []) 15 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-10-29 10:08:49 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-22 08:43:52 6 | * @Description: Table 7 | */ 8 | import videoPlay from './video-play/main.vue'; 9 | 10 | function install(app) { 11 | app.component(videoPlay.name, videoPlay) 12 | } 13 | videoPlay.install = install 14 | export { 15 | videoPlay, 16 | install 17 | } 18 | export default videoPlay; -------------------------------------------------------------------------------- /lib/style/animate.less: -------------------------------------------------------------------------------- 1 | .rotating{ 2 | animation: rotating 2s linear infinite; 3 | } 4 | @keyframes rotating{ 5 | 100%{-webkit-transform:rotate(360deg);} 6 | } 7 | 8 | -------------------------------------------------------------------------------- /lib/style/base.less: -------------------------------------------------------------------------------- 1 | .iconfont{display: inline-block;} 2 | .d-flex-x, 3 | .d-flex-y, 4 | .d-flex-center { 5 | display: flex; 6 | } 7 | 8 | .d-flex-x { 9 | align-items: center; 10 | } 11 | 12 | .d-flex-y { 13 | 14 | justify-content: center; 15 | } 16 | 17 | .d-flex-center { 18 | justify-content: center; 19 | align-items: center; 20 | } 21 | 22 | .mr5 { 23 | margin-right: 5px; 24 | } 25 | 26 | .mr10 { 27 | margin-right: 10px; 28 | } 29 | 30 | .ml5 { 31 | margin-left: 5px; 32 | } 33 | 34 | .ml10 { 35 | margin-left: 10px; 36 | } 37 | 38 | .d-pointer { 39 | cursor: pointer; 40 | } -------------------------------------------------------------------------------- /lib/style/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2178361 */ 3 | src: url('iconfont.woff2?t=1629866025665') format('woff2') 4 | } 5 | 6 | .iconfont { 7 | font-family: "iconfont" !important; 8 | font-size: 16px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | .icon-replay:before { 15 | content: "\e631"; 16 | } 17 | 18 | .icon-pip:before { 19 | content: "\e820"; 20 | } 21 | 22 | .icon-loading:before { 23 | content: "\e62e"; 24 | } 25 | 26 | .icon-play:before { 27 | content: "\e851"; 28 | } 29 | 30 | .icon-pause:before { 31 | content: "\e863"; 32 | } 33 | 34 | .icon-screen:before { 35 | content: "\e88f"; 36 | } 37 | 38 | .icon-web-screen:before { 39 | content: "\e609"; 40 | } 41 | 42 | .icon-settings:before { 43 | content: "\e60c"; 44 | } 45 | 46 | .icon-volume-down:before { 47 | content: "\e60d"; 48 | } 49 | 50 | .icon-volume-up:before { 51 | content: "\e60e"; 52 | } 53 | 54 | .icon-volume-mute:before { 55 | content: "\e60f"; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /lib/style/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdlumia/vue3-video-play/d1b8d34556df0182ffeb41d494fb51bcb823a6ef/lib/style/iconfont.woff2 -------------------------------------------------------------------------------- /lib/style/reset.less: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font: inherit; 92 | vertical-align: baseline; 93 | } 94 | 95 | /* HTML5 display-role reset for older browsers */ 96 | article, 97 | aside, 98 | details, 99 | figcaption, 100 | figure, 101 | footer, 102 | header, 103 | hgroup, 104 | menu, 105 | nav, 106 | section { 107 | display: block; 108 | } 109 | 110 | body { 111 | line-height: 1; 112 | } 113 | 114 | ol, 115 | ul { 116 | list-style: none; 117 | } 118 | 119 | blockquote, 120 | q { 121 | quotes: none; 122 | } 123 | 124 | blockquote:before, 125 | blockquote:after, 126 | q:before, 127 | q:after { 128 | content: ''; 129 | content: none; 130 | } 131 | 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } 136 | 137 | textarea { 138 | font-family: inherit; 139 | } -------------------------------------------------------------------------------- /lib/style/transition.less: -------------------------------------------------------------------------------- 1 | 2 | .d-fade-in-enter-active, 3 | .d-fade-in-leave-active { 4 | transition: .5s; 5 | } 6 | .d-fade-in-enter-from, 7 | .d-fade-in-leave-to { 8 | opacity: 0; 9 | } 10 | 11 | .d-scale-out-enter-active, 12 | .d-scale-out-leave-active { 13 | transition: .3s; 14 | } 15 | .d-scale-out-leave-to { 16 | transform: scale(1.3); 17 | opacity: 0; 18 | } 19 | 20 | .rotateHover{ 21 | transition: .2s; 22 | &:hover{ 23 | transform: rotate(90deg); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/style/vPlayer.less: -------------------------------------------------------------------------------- 1 | .d-player-wrap { 2 | position: relative; 3 | overflow: hidden; 4 | background-color: #000; 5 | 6 | &.web-full-screen { 7 | z-index: 9999999; 8 | position: fixed; 9 | left: 0; 10 | top: 0; 11 | width: 100vw !important; 12 | height: 100vh !important; 13 | } 14 | 15 | .d-player-video { 16 | position: relative; 17 | z-index: 1; 18 | width: 100%; 19 | height: 100%; 20 | .d-player-video-poster{ 21 | position: absolute; 22 | height: 100%; 23 | width: 100%; 24 | top:0; 25 | left:0; 26 | img{ 27 | display: block; 28 | width: 100%; 29 | height: 100%; 30 | object-fit: cover; 31 | } 32 | } 33 | 34 | .d-player-video-main { 35 | width: 100%; 36 | height: 100%; 37 | transition: .2s; 38 | 39 | 40 | &.video-mirror { 41 | transform: rotateY(180deg); 42 | } 43 | } 44 | 45 | } 46 | 47 | .d-player-control { 48 | transition: .1s; 49 | transform: translateY(40px); 50 | position: absolute; 51 | z-index: 2; 52 | left: 0; 53 | bottom: 0; 54 | height: 50px; 55 | width: 100%; 56 | color: #fff; 57 | 58 | .d-control-progress { 59 | width: 100%; 60 | position: relative; 61 | height: 10px; 62 | cursor: pointer; 63 | 64 | .d-progress-bar { 65 | position: absolute; 66 | left: 0; 67 | right: 0; 68 | bottom: 0; 69 | width: 100%; 70 | transition: height 0.1s; 71 | height: 3px; 72 | z-index: 1; 73 | 74 | // pointer-events: none; 75 | //对 d-slider内的组件样式修改 76 | :deep(.d-slider__runway) { 77 | transition: height 0.1s; 78 | height: 100%; 79 | 80 | .d-slider__bar::before { 81 | transform: translateY(-50%) scale(0, 0); 82 | } 83 | } 84 | } 85 | 86 | &:hover { 87 | .d-progress-bar { 88 | height: 100%; 89 | 90 | :deep(.d-slider__bar::before) { 91 | transform: translateY(-50%) scale(1, 1) !important; 92 | } 93 | } 94 | } 95 | } 96 | 97 | .d-control-tool { 98 | position: absolute; 99 | padding: 0 10px; 100 | box-sizing: border-box; 101 | background: rgba(0, 0, 0, 0.8); 102 | display: flex; 103 | justify-content: space-between; 104 | align-items: center; 105 | top: 10px; 106 | left: 0; 107 | bottom: 0; 108 | width: 100%; 109 | box-sizing: border-box; 110 | 111 | .d-tool-bar { 112 | display: flex; 113 | height: 100%; 114 | 115 | .d-tool-item { 116 | position: relative; 117 | height: 100%; 118 | cursor: pointer; 119 | text-align: center; 120 | padding: 0 8px; 121 | display: flex; 122 | align-items: center; 123 | font-size: 13px; 124 | 125 | .d-tool-item-main { 126 | position: absolute; 127 | white-space: nowrap; 128 | z-index: 2; 129 | bottom: 98%; 130 | left: 50%; 131 | padding: 6px 16px; 132 | box-sizing: border-box; 133 | display: none; 134 | background: rgba(0, 0, 0, 0.95); 135 | border-radius: 5px; 136 | transform: translateX(-50%); 137 | } 138 | 139 | &:hover { 140 | .d-tool-item-main { 141 | display: flex; 142 | } 143 | } 144 | } 145 | } 146 | 147 | // 时间 148 | .d-tool-time { 149 | font-size: 12px; 150 | color: #fff; 151 | font-weight: 300; 152 | 153 | .total-time { 154 | color: rgba(255, 255, 255, 0.8); 155 | } 156 | } 157 | 158 | // 音量 159 | .volume-box { 160 | height: 160px; 161 | width: 50px; 162 | display: flex; 163 | align-items: center; 164 | justify-content: center; 165 | 166 | .volume-main { 167 | height: 90%; 168 | display: flex; 169 | width: 60px; 170 | flex-direction: column; 171 | align-items: center; 172 | 173 | .volume-text-size { 174 | margin-bottom: 10px; 175 | font-size: 12px; 176 | font-weight: 400; 177 | } 178 | 179 | &.is-muted { 180 | :deep(.d-slider__bar) { 181 | height: 0 !important 182 | } 183 | } 184 | } 185 | } 186 | 187 | .speed-main { 188 | padding: 0 10px; 189 | 190 | li { 191 | cursor: pointer; 192 | line-height: 34px; 193 | font-size: 12px; 194 | color: #fff; 195 | 196 | &:hover { 197 | opacity: .8; 198 | } 199 | 200 | &.speed-active { 201 | color: rgba(var(--primary-color), 1); 202 | font-weight: bold; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | 209 | &.d-player-wrap-hover { 210 | .d-player-control { 211 | transform: translateY(0px); 212 | } 213 | } 214 | } 215 | 216 | .d-player-state, 217 | .d-player-input { 218 | position: absolute; 219 | left: 0; 220 | top: 0; 221 | right: 0; 222 | bottom: 40px; 223 | display: flex; 224 | justify-content: center; 225 | align-items: center; 226 | overflow: hidden; 227 | z-index: 1; 228 | } 229 | 230 | .d-player-input { 231 | width: 100%; 232 | border: none; 233 | opacity: 0; 234 | cursor: default; 235 | 236 | } 237 | 238 | .d-play-btn { 239 | width: 90px; 240 | height: 90px; 241 | color: #fff; 242 | display: flex; 243 | align-items: center; 244 | justify-content: center; 245 | background-color: rgba(0, 0, 0, .7); 246 | border-radius: 50%; 247 | } 248 | 249 | 250 | // 黑幕 251 | .d-player-lightoff { 252 | position: fixed; 253 | left: 0; 254 | top: 0; 255 | width: 100vw; 256 | height: 100vh; 257 | background-color: rgba(0, 0, 0, .9); 258 | } 259 | 260 | // 关灯模式 261 | .is-lightoff { 262 | z-index: 999998; 263 | } -------------------------------------------------------------------------------- /lib/utils/dom.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-23 21:17:54 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-26 14:03:35 6 | * @Description: file content 7 | */ 8 | export const on = function ( 9 | element: Element | HTMLElement | Document | Window, 10 | event: string, 11 | handler: EventListenerOrEventListenerObject, 12 | useCapture = false, 13 | ): void { 14 | if (element && event && handler) { 15 | element.addEventListener(event, handler, useCapture) 16 | } 17 | } 18 | /* istanbul ignore next */ 19 | export const off = function ( 20 | element: Element | HTMLElement | Document | Window, 21 | event: string, 22 | handler: EventListenerOrEventListenerObject, 23 | useCapture = false, 24 | ): void { 25 | if (element && event && handler) { 26 | element.removeEventListener(event, handler, useCapture) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/utils/util.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-19 12:50:35 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-26 09:50:44 6 | * @Description: file content 7 | */ 8 | 9 | // hex转rgb 10 | export const hexToRgba = (hex) => { 11 | return `${parseInt("0x" + hex.slice(1, 3))},${parseInt( 12 | "0x" + hex.slice(3, 5) 13 | )},${parseInt("0x" + hex.slice(5, 7))}`; 14 | } 15 | export const firstUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1) 16 | // 电影时间格式化 17 | export const timeFormat = (time) => { 18 | let hh: any = ~~(time / 3600); 19 | let mm: any = ~~((time % 3600) / 60); 20 | let ss: any = ~~(time % 60); //取整 21 | hh = hh < 10 ? "0" + hh : hh; //个位数补0 22 | mm = mm < 10 ? "0" + mm : mm; //个位数补0 23 | ss = ss < 10 ? "0" + ss : ss; //个位数补0 24 | return `${hh}:${mm}:${ss}`; 25 | } 26 | // 是否是移动端 27 | export const isMobile = !!("ontouchstart" in window) 28 | // 全屏模式 29 | export const toggleFullScreen = (el) => { 30 | //如果当前是全屏状态,就退出全屏,否则进入全屏状态 31 | //获取当前的全屏状态 32 | let documentEL = (document as any) 33 | let isFullscreen = documentEL.webkitIsFullScreen || documentEL.fullscreen; 34 | if (!isFullscreen) { 35 | const inFun = 36 | el.requestFullscreen || el.webkitRequestFullScreen; 37 | //让当前播放器进入全屏状态 38 | inFun.call(el); 39 | } else { 40 | const exitFun = 41 | document.exitFullscreen || documentEL.webkitExitFullScreen; 42 | //退出全屏状态要使用document 43 | exitFun.call(documentEL); 44 | } 45 | return !isFullscreen 46 | 47 | } 48 | // 画中画模式 49 | export const requestPictureInPicture = (el: HTMLElement | Document | Window) => { 50 | if ((document as any).pictureInPictureElement) { 51 | (document as any).exitPictureInPicture().catch((error) => { 52 | console.log(error, "Video failed to leave Picture-in-Picture mode."); 53 | }); 54 | 55 | } else { 56 | //开启 57 | (el as any).requestPictureInPicture().catch((error) => { 58 | console.log(error, "Video failed to enter Picture-in-Picture mode."); 59 | }); 60 | } 61 | } -------------------------------------------------------------------------------- /lib/video-play/main.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-11-03 16:29:47 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 15:50:42 6 | * @Description: file content 7 | */ 8 | 9 | 257 | 263 | 622 | 623 | 624 | -------------------------------------------------------------------------------- /lib/video-play/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-25 11:19:35 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-09-02 16:03:36 6 | * @Description: file content 7 | */ 8 | import type { PropType } from "vue"; 9 | export const videoEmits = [ 10 | "loadstart", 11 | "play", 12 | "pause", 13 | "playing", 14 | "seeking", 15 | "seeked", 16 | "waiting", 17 | "durationchange", 18 | "progress", 19 | "canplay", 20 | "timeupdate", 21 | "ended", 22 | "error", 23 | "stalled", 24 | ]; 25 | export const defineProps = { 26 | width: { type: String, default: "800px" }, 27 | height: { type: String, default: "450px" }, 28 | color: { type: String, default: "#409eff" }, 29 | src: { required: true, type: String, default: "" }, //视频源 30 | title: { type: String, default: "" }, //视频名称 31 | type: { type: String, default: "video/mp4" }, //视频类型 32 | poster: { type: String, default: "" }, //封面 33 | webFullScreen: { type: Boolean, default: false }, //网页全屏 34 | speed: { type: Boolean, default: true }, //是否支持快进快退 //移动端不支持 35 | currentTime: { type: Number, default: 0 }, //当前播放时间 36 | playsinline: { type: Boolean, default: false }, //ios端 点击播放是否全屏 37 | muted: { type: Boolean, default: false }, //静音 38 | speedRate: { 39 | type: Array, 40 | default: () => ["2.0", "1.5", "1.25", "1.0", "0.75", "0.5"], 41 | }, //播放倍速 42 | autoPlay: { type: Boolean, default: false }, //自动播放 43 | loop: { type: Boolean, default: false }, //循环播放 44 | mirror: { type: Boolean, default: false }, //镜像画面 45 | ligthOff: { type: Boolean, default: false }, //关灯模式 46 | volume: { type: [String, Number], default: 0.3 }, //默认音量大小 47 | control: { type: Boolean, default: true }, //是否显示控制器 48 | controlBtns: { 49 | type: Array as PropType>, 50 | default: [ 51 | "audioTrack", 52 | "quality", 53 | "speedRate", 54 | "volume", 55 | "setting", 56 | "pip", 57 | "pageFullScreen", 58 | "fullScreen", 59 | ], 60 | }, //是否显示控制器 61 | preload: { type: String, default: "auto" }, //预加载 62 | }; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-video-play", 3 | "version": "1.3.2", 4 | "description": "hls.js player component for Vue3", 5 | "files": [ 6 | "dist", 7 | "lib" 8 | ], 9 | "main": "./dist/index.umd.js", 10 | "module": "./dist/index.es.js", 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vue-tsc --noEmit && vite build", 14 | "pub": "node ./scripts/publish.js", 15 | "serve": "vite preview", 16 | "docs:dev": "vitepress dev docs", 17 | "docs:build": "vitepress build docs", 18 | "docs:serve": "vitepress serve docs", 19 | "docs:pub": "sh ./scripts/gh-pages.sh" 20 | }, 21 | "author": { 22 | "name": "xdlumia", 23 | "email": "xdq@live.cn", 24 | "url": "https://codelife.cc" 25 | }, 26 | "homepage": "https://codelife.cc/vue3-video-play/", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/xdlumia/vue3-video-play.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/xdlumia/vue3-video-play/issues" 33 | }, 34 | "dependencies": { 35 | "hls.js": "^1.4.12", 36 | "throttle-debounce": "^5.0.0", 37 | "vue": "^3.2.25" 38 | }, 39 | "devDependencies": { 40 | "@vitejs/plugin-vue": "^4.4.1", 41 | "@vue/compiler-sfc": "^3.3.8", 42 | "chalk": "^5.3.0", 43 | "less": "^4.2.0", 44 | "less-loader": "^11.1.3", 45 | "readline-sync": "^1.4.10", 46 | "shelljs": "^0.8.5", 47 | "typescript": "^5.2.2", 48 | "vite": "^4.5.0", 49 | "vitepress": "^0.22.4", 50 | "vitepress-theme-demoblock": "^3.0.3", 51 | "vue-tsc": "^1.8.22", 52 | "vue3-video-play": "^1.3.1-beta.6" 53 | }, 54 | "keywords": [ 55 | "vue 3", 56 | "vue3", 57 | "vue", 58 | "vue3-video-play", 59 | "hlsjs", 60 | "hls.js", 61 | "vue video player", 62 | "vue3 video player", 63 | "video player video", 64 | "video player", 65 | "vue player", 66 | "vue video" 67 | ], 68 | "license": "ISC" 69 | } -------------------------------------------------------------------------------- /scripts/gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | ### 3 | # @Author: web.王晓冬 4 | # @Date: 2021-08-21 22:18:02 5 | # @LastEditors: web.王晓冬 6 | # @LastEditTime: 2021-09-01 15:27:27 7 | # @Description: file content 8 | ### 9 | 10 | # 确保脚本抛出遇到的错误 11 | set -e 12 | 13 | # 生成静态文件 14 | yarn run docs:build 15 | 16 | # 进入生成的文件夹 17 | cd ../docs/.vitepress/dist 18 | 19 | # 如果是发布到自定义域名 20 | # echo 'www.example.com' > CNAME 21 | 22 | git init 23 | git add -A 24 | git commit -m 'deploy to the gh-pages' 25 | 26 | # 如果发布到 https://.github.io 27 | # git push -f git@github.com:xdlumia/vue3-video-play.git gh-pages 28 | # git push -f git@github.com:xdlumia/vue3-video-play.git master:gh-pages 29 | # git push -f git@github.com:xdlumia/xdlumia.github.io.git master:gh-pages 30 | # git push -f https://github.com/xdlumia/vue3-video-play.git master:gh-pages 31 | git push -f https://github.com/xdlumia/xdlumia.github.io.git master:gh-pages 32 | -------------------------------------------------------------------------------- /scripts/publish copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-03-18 12:36:57 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-25 20:22:03 6 | * @Description: file content 7 | */ 8 | // shell字体颜色 默认=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,紫色=35,天蓝色=36,白色=3 9 | 10 | const shell = require('shelljs'); 11 | const readlineSync = require('readline-sync'); 12 | const path = require('path'); 13 | let packageJSON = require(path.resolve('package.json')); 14 | const chalk = require('chalk'); 15 | 16 | const defaultLog = (log) => console.log(chalk.blue(`------${log}-----`)) 17 | const errorLog = (log) => console.log(chalk.red(`------${log}-----`)) 18 | const successLog = (log) => console.log(chalk.green(`------${log}-----`)) 19 | // 当前版本 20 | const currentVersion = packageJSON.version 21 | // 版本标识 22 | const [vrsionFlag] = process.argv.slice(2) 23 | // 获取git当前分支 24 | let currentBranch = shell.exec('git symbolic-ref --short -q HEAD', { 25 | async: false, 26 | silent: true 27 | }).stdout.trim(); 28 | if (currentBranch != 'dev') { 29 | shell.echo("\033[1;31m Error: 当前是 " + currentBranch + " 分支 请切换到dev分支\033[0m"); 30 | return 31 | } 32 | 33 | // 新版本 34 | var confirm = readlineSync.question(`Current is "v${currentVersion}".\n\ 35 | // -- p:patch m:minor s:major n:Exit default:patch 36 | // -- are you sure? (p/s/m/n)`) 37 | // 直接升级小号 38 | if (confirm.trim() == '' || confirm.trim().toLowerCase() == 'p') { 39 | shell.exec('npm version patch') 40 | } 41 | // 则升级一位中号,大号不动,小号置为空 42 | else if (confirm.trim().toLowerCase() == 'm') { 43 | shell.exec('npm version minor') 44 | } 45 | // 升级一位大号,其他位都置为0 46 | else if (confirm.trim().toLowerCase() == 's') { 47 | shell.exec('npm version major') 48 | } else { 49 | shell.echo("\033[1;31m Error: 输入错误 已自动退出\033[0m") 50 | shell.exit() 51 | } 52 | 53 | shell.exec('git checkout master'); 54 | shell.exec('git pull'); 55 | shell.exec('git merge dev'); 56 | if (shell.exec('git push origin master --tags').code != 0) { 57 | shell.echo("\033[1;31mError: git push ogigin master 失败! 已退出\033[0m"); 58 | shell.exec('git checkout dev'); 59 | shell.exit() 60 | return 61 | } 62 | 63 | shell.exec('git checkout dev'); 64 | shell.exec('git rebase master'); 65 | // shell.exec('git push origin dev'); 66 | if (shell.exec('git push origin dev').code != 0) { 67 | shell.echo("\033[1;31mError: git push origin dev 失败! 已退出\033[0m"); 68 | shell.exec('git checkout dev'); 69 | shell.exit() 70 | return 71 | } 72 | if (shell.exec('npm publish').code != 0) { 73 | shell.echo("\033[1;31mError: npm publish 失败! 已退出\033[0m"); 74 | shell.exit() 75 | return 76 | } 77 | shell.echo("\033[1;32mSuccess Publish success!\033[0m"); 78 | shell.exit() -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2020-03-18 12:36:57 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-27 10:54:53 6 | * @Description: file content 7 | */ 8 | // shell字体颜色 默认=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,紫色=35,天蓝色=36,白色=3 9 | 10 | const shell = require('shelljs'); 11 | const readlineSync = require('readline-sync'); 12 | const path = require('path'); 13 | let packageJSON = require(path.resolve('package.json')); 14 | 15 | const defaultLog = (log) => console.log(`--------------${log}---------`) 16 | const errorLog = (log) => console.log('\x1B[31m%s\x1B[0m', `--------------${log}-----------`) 17 | const successLog = (log) => console.log('\x1B[32m%s\x1B[0m', `---------------${log}--------`) 18 | 19 | // 当前版本 20 | const currentVersion = packageJSON.version 21 | // 版本标识 22 | const [commitInfo] = process.argv.slice(2) || 'auto commit' 23 | // 获取git当前分支 24 | let currentBranch = shell.exec('git symbolic-ref --short -q HEAD', { 25 | async: false, 26 | silent: true 27 | }).stdout.trim(); 28 | if (currentBranch != 'dev') { 29 | errorLog(`当前是${currentBranch}分支 请切换到dev分支`) 30 | // shell.echo("\033[1;31m Error: 当前是 " + currentBranch + " 分支 请切换到dev分支\033[0m"); 31 | return 32 | } 33 | // shell.exec('git add .'); 34 | // shell.exec(`git commit -m ${commitInfo}`); 35 | // shell.exec('git push'); 36 | // successLog('dev分支提交成功') 37 | // shell.exec('git checkout main'); 38 | // shell.exec('git pull'); 39 | // shell.exec('git merge dev'); 40 | // defaultLog('dev分支合并到main分支') 41 | // if (shell.exec('git push origin main --tags').code != 0) { 42 | // // shell.echo("\033[1;31mError: git push ogigin main 失败! 已退出\033[0m"); 43 | // errorLog(`git push ogigin main 失败! 已退出 已`) 44 | // shell.exec('git checkout dev'); 45 | // shell.exit() 46 | // return 47 | // } 48 | // successLog('main分支提交成功') 49 | // shell.exec('git checkout dev'); 50 | // shell.exec('git push origin dev'); 51 | 52 | 53 | // 新版本 54 | var confirm = readlineSync.question(`Current is "v${currentVersion}".\n\ 55 | // -- p:patch m:minor s:major n:Exit default:patch 56 | // -- are you sure? (p/s/m/n)`) 57 | // 直接升级小号 58 | if (confirm.trim() == '' || confirm.trim().toLowerCase() == 'p') { 59 | shell.exec('npm version patch') 60 | } 61 | // 则升级一位中号,大号不动,小号置为空 62 | else if (confirm.trim().toLowerCase() == 'm') { 63 | shell.exec('npm version minor') 64 | } 65 | // 升级一位大号,其他位都置为0 66 | else if (confirm.trim().toLowerCase() == 's') { 67 | shell.exec('npm version major') 68 | } else { 69 | errorLog(`输入错误 已自动退出`) 70 | // shell.echo("\033[1;31m Error: 输入错误 已自动退出\033[0m") 71 | shell.exit() 72 | } 73 | 74 | shell.exec('yarn build'); 75 | 76 | if (shell.exec('npm pub').code != 0) { 77 | shell.echo("\033[1;31mError: npm publish 失败! 已退出\033[0m"); 78 | shell.exit() 79 | return 80 | } 81 | // shell.echo("\033[1;32mSuccess Publish success!\033[0m"); 82 | successLog('Publish success!') 83 | shell.exit() -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-20 19:10:57 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 15:38:31 6 | * @Description: file content 7 | */ 8 | 26 | 27 | 67 | 68 | 70 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-19 10:25:40 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-20 20:07:18 6 | * @Description: file content 7 | */ 8 | import { createApp } from 'vue' 9 | import App from './App.vue' 10 | 11 | const app = createApp(App) 12 | 13 | 14 | app.mount('#app') 15 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-22 10:47:24 4 | * @LastEditors: web.王晓冬 5 | * @LastEditTime: 2021-08-23 19:37:14 6 | * @Description: file content 7 | */ -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "noImplicitAny": false, 11 | "esModuleInterop": true, 12 | "lib": [ 13 | "esnext", 14 | "dom" 15 | ] 16 | }, 17 | "allowJs": true, 18 | "include": [ 19 | "src/**/*.ts", 20 | "src/**/*.d.ts", 21 | "src/**/*.tsx", 22 | "src/**/*.vue" 23 | ] 24 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: web.王晓冬 3 | * @Date: 2021-08-19 10:25:40 4 | * @LastEditors: itab.link 5 | * @LastEditTime: 2023-11-09 15:27:03 6 | * @Description: file content 7 | */ 8 | import { defineConfig } from "vite"; 9 | import vue from "@vitejs/plugin-vue"; 10 | 11 | // https://vitejs.dev/config/ 12 | export default defineConfig({ 13 | plugins: [vue()], 14 | server: { 15 | port: 3005, 16 | open: true, 17 | // 反向代理 18 | proxy: { 19 | "/api": { 20 | target: "http://xxx.xxxxx.xxx/", 21 | changeOrigin: true, 22 | rewrite: (path) => path.replace(/^\/api/, ""), 23 | }, 24 | }, 25 | }, 26 | build: { 27 | terserOptions: { 28 | compress: { 29 | drop_debugger: true, 30 | }, 31 | }, 32 | lib: { 33 | entry: "./lib/index.js", 34 | name: "index", 35 | fileName: "index", 36 | }, 37 | rollupOptions: { 38 | // 确保外部化处理那些你不想打包进库的依赖 39 | external: ["vue"], 40 | output: { 41 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 42 | globals: { 43 | vue: "Vue", 44 | }, 45 | }, 46 | }, 47 | // hmr: { overlay: false } 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Users\admin\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js add vue3-video-play@1.3.0-rc.2 3 | 4 | PATH: 5 | C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\nodejs\;C:\Program Files\Git\cmd;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Users\admin\AppData\Local\Microsoft\WindowsApps;C:\Users\admin\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\admin\AppData\Roaming\npm;C:\Users\admin\AppData\Roaming\nrm; 6 | 7 | Yarn version: 8 | 1.22.11 9 | 10 | Node version: 11 | 14.17.1 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | Error: EPERM: operation not permitted, unlink 'E:\github\vue3-video-play\node_modules\esbuild\esbuild.exe' 18 | 19 | npm manifest: 20 | { 21 | "name": "vue3-video-play", 22 | "version": "1.3.0-rc.2", 23 | "description": "hls.js player component for Vue3", 24 | "files": [ 25 | "dist", 26 | "lib" 27 | ], 28 | "main": "./dist/index.umd.js", 29 | "module": "./dist/index.es.js", 30 | "scripts": { 31 | "dev": "vite", 32 | "build": "vue-tsc --noEmit && vite build", 33 | "pub": "node ./scripts/publish.js", 34 | "serve": "vite preview", 35 | "docs:dev": "vitepress dev docs", 36 | "docs:build": "vitepress build docs", 37 | "docs:serve": "vitepress serve docs", 38 | "docs:pub": "sh ./scripts/gh-pages.sh" 39 | }, 40 | "author": { 41 | "name": "xdlumia", 42 | "email": "214005111@qq.com", 43 | "url": "https://codelife.cc" 44 | }, 45 | "homepage": "https://codelife.cc/vue3-video-play/", 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/xdlumia/vue3-video-play.git" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/xdlumia/vue3-video-play/issues" 52 | }, 53 | "dependencies": { 54 | "hls.js": "^1.0.10", 55 | "throttle-debounce": "^3.0.1", 56 | "vue": "^3.2.2" 57 | }, 58 | "devDependencies": { 59 | "@vitejs/plugin-vue": "^1.4.0", 60 | "@vue/compiler-sfc": "^3.2.2", 61 | "chalk": "^4.1.2", 62 | "less": "^4.1.1", 63 | "less-loader": "^10.0.1", 64 | "readline-sync": "^1.4.10", 65 | "shelljs": "^0.8.4", 66 | "typescript": "^4.3.5", 67 | "vite": "^2.5.0", 68 | "vitepress": "^0.16.1", 69 | "vitepress-theme-demoblock": "^1.1.1", 70 | "vue-tsc": "^0.2.3", 71 | "vue3-video-play": "^1.2.52" 72 | }, 73 | "keywords": [ 74 | "vue 3", 75 | "vue3", 76 | "vue", 77 | "vue3-video-play", 78 | "hlsjs", 79 | "hls.js", 80 | "vue video player", 81 | "vue3 video player", 82 | "video player video", 83 | "video player", 84 | "vue player", 85 | "vue video" 86 | ], 87 | "license": "ISC" 88 | } 89 | 90 | yarn manifest: 91 | No manifest 92 | 93 | Lockfile: 94 | No lockfile 95 | --------------------------------------------------------------------------------