mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-15 05:46:43 +08:00
2022年5月29日从svn移到git
This commit is contained in:
107
src/components/VideoPlayer/README.md
Normal file
107
src/components/VideoPlayer/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# vue弹幕视频播放器
|
||||
简体中文 / [English](https://github.com/yleencc/vue-barrage-videoplayer/blob/master/README_EN.md)
|
||||
|
||||
## 简介
|
||||
> 一个基于Vue的弹幕视频播放器组件。适用于:`PC版网页`、`静态资源视频`
|
||||
|
||||
## 功能支持
|
||||
> 主要功能:播放、音量控制、倍速、进度条控制、全屏、弹幕(部分)
|
||||
>
|
||||
> 键盘控制:F全屏,↑↓键调整音量,←→调整进度,Space暂停/继续
|
||||
|
||||
### 弹幕支持情况
|
||||
> 格式:只支持B站xml弹幕格式,详细格式情况參考[这篇文章](https://blog.csdn.net/Enderman_xiaohei/article/details/86659064)
|
||||
>
|
||||
> 操作:目前只能显示弹幕不能做其他操作
|
||||
|
||||
## 预览示例
|
||||
- [点击跳转预览](https://yleen.cc/files/works/barrage-video-player/)
|
||||
|
||||
---
|
||||
|
||||
## 计划表
|
||||
- [X] 多播放器共存
|
||||
- [X] 弹幕(开发了一小部分)
|
||||
- [ ] 网页全屏
|
||||
- [ ] Vue升级到3.x版本
|
||||
- [ ] 右键查看视频数据
|
||||
- [ ] 多语言支持
|
||||
- [ ] 推流视频,websocket
|
||||
- [ ] 适配好移动端
|
||||
|
||||
## 更新历史
|
||||
- 0.1.1 增加了对同时使用多个播放器的支持,以及封面图
|
||||
- 0.1.2 优化了遗留的部分烂代码
|
||||
- 0.2.0 支持显示滑动弹幕,预留导入外部弹幕文件功能
|
||||
|
||||
## 使用方式与示例
|
||||
可参考[preview-videoplayer.vue](https://github.com/yleencc/vue-barrage-videoplayer/blob/master/src/views/preview-videoplayer.vue) 文件
|
||||
1. 将`src/components`下的四个文件复制到你的项目里,确保这四个文件保持在同一目录;
|
||||
2. 将`src/assets`文件夹复制到项目`src/`里面,确保`/src/assets/images/loading.svg`存在(该文件为缓冲时的加载图标)
|
||||
3. 在页面中引用`barrage-videoplayer.vue`
|
||||
- 注:项目需要引入`axios`
|
||||
``` vue
|
||||
<template>
|
||||
<barrageVideoplayer
|
||||
:src="video_src"
|
||||
:cover="cover"
|
||||
:biBarrageXml="biBarrageXml"
|
||||
width="100%"
|
||||
height="100%"
|
||||
></barrageVideoplayer>
|
||||
</template>
|
||||
<script>
|
||||
import barrageVideoplayer from "../components/barrage-videoplayer.vue";
|
||||
export default {
|
||||
components: {barrageVideoplayer},
|
||||
data() {
|
||||
return {
|
||||
video_src: "",
|
||||
cover: "",
|
||||
biBarrageXml: "",
|
||||
};
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 传入参数
|
||||
`barrage-videoplayer.vue`
|
||||
|
||||
| 字段 | 是否可选 | 类型 | 默认值 | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| src | `必选` | String | null | 视频链接 |
|
||||
| cover | `可选` | String | null | 封面图的链接 |
|
||||
| primaryColor | `可选` | String | "cornflowerblue" | 主题色,可使用css属性值:颜色名称、#十六进制、rgb、rgba、HSL、HSLA |
|
||||
| width | `可选` | String | 100% | 视频宽度,对应css样式 |
|
||||
| height | `可选` | String | 100% | 视频高度,对应css样式 |
|
||||
| speed_list | `可选` | array | ["2.0", "1.5", "1.25", "1.0", "0.75", "0.5"] | 倍速选择的列表 |
|
||||
| biBarrageXml | `可选` | String | null | 弹幕链接,使用的是B站XML风格,需处理跨域问题 |
|
||||
|
||||
### bug反馈
|
||||
- 提[issue](https://github.com/yleencc/vue-barrage-videoplayer/issues)
|
||||
- 本人首页的联系方式,邮箱优先
|
||||
|
||||
---
|
||||
|
||||
### Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
#### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
#### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
#### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
19
src/components/VideoPlayer/images/loading.svg
Normal file
19
src/components/VideoPlayer/images/loading.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="150px" height="150px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="84" cy="50" r="10" fill="#d2d2cb">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="0.41666666666666663s" calcMode="spline" keyTimes="0;1" values="10;0" keySplines="0 0.5 0.5 1" begin="0s"></animate>
|
||||
<animate attributeName="fill" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="discrete" keyTimes="0;0.25;0.5;0.75;1" values="#d2d2cb;#dae8e5;#83a79d;#4d695d;#d2d2cb" begin="0s"></animate>
|
||||
</circle><circle cx="16" cy="50" r="10" fill="#d2d2cb">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s"></animate>
|
||||
</circle><circle cx="50" cy="50" r="10" fill="#4d695d">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.41666666666666663s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.41666666666666663s"></animate>
|
||||
</circle><circle cx="84" cy="50" r="10" fill="#83a79d">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.8333333333333333s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.8333333333333333s"></animate>
|
||||
</circle><circle cx="16" cy="50" r="10" fill="#dae8e5">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;10;10;10" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.2499999999999998s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="1.6666666666666665s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.2499999999999998s"></animate>
|
||||
</circle>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
788
src/components/VideoPlayer/index.vue
Normal file
788
src/components/VideoPlayer/index.vue
Normal file
@@ -0,0 +1,788 @@
|
||||
<!-- 传入参数(props)-->
|
||||
<!-- 必填项:src,-->
|
||||
<!-- 可选项:width, height, speedList, cover, biBarrageXml-->
|
||||
<template>
|
||||
<div @contextmenu.prevent
|
||||
ref="area"
|
||||
:style="`--primaryColor: ${primaryColor}`"
|
||||
:class="{'player-area': true, 'player-fullscreen': isFullscreen, 'cursor-lasting-static': isCursorStatic}"
|
||||
>
|
||||
<!-- 视频主体 -->
|
||||
<video
|
||||
ref="video"
|
||||
class="player-video cursor-pointer"
|
||||
@click="togglePlayStatus"
|
||||
@keydown.f.prevent="toggleFullScreen"
|
||||
@keydown.space.prevent="togglePlayStatus"
|
||||
@keydown.left.prevent="backwardCurrentTime"
|
||||
@keydown.right.prevent="forwardCurrentTime"
|
||||
@keydown.up.prevent="increaseVolume"
|
||||
@keydown.down.prevent="lowerVolume"
|
||||
@keydown.esc.prevent="toggleFullScreen"
|
||||
tabindex="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<source :src="src" />
|
||||
</video>
|
||||
<!-- 封面图 -->
|
||||
<div v-if="cover!=null && isShowCover" class="player-cover">
|
||||
<img :src="cover" :width="width" :height="height" alt=""/>
|
||||
</div>
|
||||
<!-- 弹幕 -->
|
||||
<playerBarrageScreen
|
||||
v-if="biBarrageXml != null"
|
||||
:videoDom="videoDom"
|
||||
:barrageTimelineStart="barrageTimelineStart"
|
||||
:isPlaying="isPlaying"
|
||||
:biBarrageXml="biBarrageXml"
|
||||
/>
|
||||
<!-- 加载动画 -->
|
||||
<div v-show="isShowLoading" class="player-loading" @click="videoDom.focus({preventScroll: true})">
|
||||
<img src="@/components/VideoPlayer/images/loading.svg" alt="loading"/>
|
||||
</div>
|
||||
<!-- 控制栏 -->
|
||||
<div class="player-controls-container" @click="videoDom.focus({preventScroll: true})">
|
||||
<div v-show="isShowVolumeHint" class="player-volumeHint">
|
||||
<span class="player-volumeHint-text">当前音量:{{volumePercent}}%</span>
|
||||
</div>
|
||||
<div class="player-paused-state">
|
||||
<svg
|
||||
v-show="!isPlaying"
|
||||
t="1596553913647"
|
||||
class="player-controls-icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="13502"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M897.143467 597.051733l-464.648534 311.5264c-46.976 31.488-110.592 18.944-142.08-28.023466A102.4 102.4 0 0 1 273.066667 823.5264V200.4736c0-56.5504 45.8496-102.4 102.4-102.4a102.4 102.4 0 0 1 57.028266 17.348267l464.64 311.5264c46.976 31.488 59.528533 95.104 28.032 142.08a102.4 102.4 0 0 1-28.023466 28.023466z"
|
||||
p-id="13503"
|
||||
fill="rgb(0,0,0,0.8)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="{'player-controls': true, 'cursor-lasting-static': isCursorStatic}">
|
||||
<div class="player-progress-bar">
|
||||
<progressBar
|
||||
:currentProgress="currentProgress"
|
||||
v-on:updateProgress="updateProgressByClickBar"
|
||||
v-on:getMouseDownStatus="getMouseDownStatusOfProgressBar"
|
||||
:isDrag="isDrag"
|
||||
width="100%"
|
||||
height="4px"
|
||||
></progressBar>
|
||||
</div>
|
||||
<div class="player-controls-bottom">
|
||||
<div class="player-controls-bottom-left">
|
||||
<div class="player-controls-btn cursor-pointer" @click="togglePlayStatus">
|
||||
<svg
|
||||
v-show="!isPlaying"
|
||||
t="1596553913647"
|
||||
class="player-controls-icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="13502"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M897.143467 597.051733l-464.648534 311.5264c-46.976 31.488-110.592 18.944-142.08-28.023466A102.4 102.4 0 0 1 273.066667 823.5264V200.4736c0-56.5504 45.8496-102.4 102.4-102.4a102.4 102.4 0 0 1 57.028266 17.348267l464.64 311.5264c46.976 31.488 59.528533 95.104 28.032 142.08a102.4 102.4 0 0 1-28.023466 28.023466z"
|
||||
p-id="13503"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-show="isPlaying"
|
||||
t="1596554115916"
|
||||
class="player-controls-icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="17417"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M268.97201558 114.31784297c73.21218086 0 132.56071902 59.34853814 132.56071901 132.560719v530.24287606c0 73.21218086-59.34853814 132.56071902-132.56071901 132.560719s-132.56071902-59.34853814-132.56071901-132.560719V246.87856197c0-73.21218086 59.34853814-132.56071902 132.56071901-132.560719z m486.05596884 0c73.21218086 0 132.56071902 59.34853814 132.56071901 132.560719v530.24287606c0 73.21218086-59.34853814 132.56071902-132.56071901 132.560719s-132.56071902-59.34853814-132.56071901-132.560719V246.87856197c0-73.21218086 59.34853814-132.56071902 132.56071901-132.560719z"
|
||||
p-id="17418"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="player-time">{{ currentTimeFormat }} / {{ fullTimeFormat }}</div>
|
||||
</div>
|
||||
<div class="player-controls-bottom-right">
|
||||
<div class="player-controls-btn cursor-pointer btn-speed">
|
||||
<span>{{currentSpeed === 1 ? '倍速' : `${currentSpeed}x`}}</span>
|
||||
<div class="speed-control">
|
||||
<ul class="speed-control-list">
|
||||
<li
|
||||
v-for="item in speedList"
|
||||
:key="item"
|
||||
@click="changeSpeed"
|
||||
:data-value="item"
|
||||
:class="{'current': currentSpeed === Number(item)}"
|
||||
>{{ item }}x</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-controls-btn btn-volume">
|
||||
<div class="cursor-pointer">
|
||||
<svg
|
||||
t="1596553801150"
|
||||
class="player-controls-icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="12518"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M308.971 657.987l150.28 165.279a16 16 0 0 0 11.838 5.236c8.837 0 16-7.163 16-16v-600.67a16 16 0 0 0-5.236-11.839c-6.538-5.944-16.657-5.463-22.602 1.075l-150.28 165.279A112 112 0 0 1 226.105 403H177c-17.673 0-32 14.327-32 32v154.333c0 17.674 14.327 32 32 32h49.105a112 112 0 0 1 82.866 36.654zM177 701.333c-61.856 0-112-50.144-112-112V435c0-61.856 50.144-112 112-112h49.105a32 32 0 0 0 23.676-10.472l150.28-165.28c35.668-39.227 96.383-42.113 135.61-6.445a96 96 0 0 1 31.418 71.028v600.671c0 53.02-42.98 96-96 96a96 96 0 0 1-71.029-31.417l-150.28-165.28a32 32 0 0 0-23.675-10.472H177z m456.058-348.336c-18.47-12.118-23.621-36.915-11.503-55.386 12.118-18.471 36.916-23.621 55.387-11.503C752.495 335.675 799 419.908 799 512c0 92.093-46.505 176.325-122.058 225.892-18.471 12.118-43.269 6.968-55.387-11.503-12.118-18.471-6.968-43.268 11.503-55.386C686.303 636.07 719 576.848 719 512c0-64.848-32.697-124.07-85.942-159.003z m92.93-137.323c-18.07-12.71-22.415-37.66-9.706-55.73s37.66-22.415 55.73-9.706C888.942 232.478 960 366.298 960 512s-71.058 279.522-187.988 361.762c-18.07 12.71-43.021 8.364-55.73-9.706-12.709-18.07-8.363-43.02 9.706-55.73C821.838 740.912 880 631.38 880 512c0-119.38-58.161-228.912-154.012-296.326z"
|
||||
p-id="12519"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="volume-control">
|
||||
<div class="volume-control-wrap">
|
||||
<div class="volume-text">{{ volumePercent }}</div>
|
||||
<volumeBar
|
||||
:currentVolume="currentVolume"
|
||||
v-on:updateVolume="updateVolumeByClickBar"
|
||||
width="4px"
|
||||
></volumeBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-controls-btn cursor-pointer" @click="toggleFullScreen">
|
||||
<svg
|
||||
v-show="!isFullscreen"
|
||||
class="player-controls-icon"
|
||||
t="1596553111831"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10800"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M0 232.732444A232.732444 232.732444 0 0 1 232.732444 0h558.535112A232.732444 232.732444 0 0 1 1024 232.732444v558.535112A232.732444 232.732444 0 0 1 791.267556 1024H232.732444A232.732444 232.732444 0 0 1 0 791.267556V232.732444z m232.732444-139.662222a139.662222 139.662222 0 0 0-139.662222 139.662222v558.535112a139.662222 139.662222 0 0 0 139.662222 139.662222h558.535112a139.662222 139.662222 0 0 0 139.662222-139.662222V232.732444a139.662222 139.662222 0 0 0-139.662222-139.662222H232.732444z"
|
||||
p-id="10801"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path
|
||||
d="M549.575111 245.845333c0-25.799111 20.935111-46.734222 46.734222-46.734222h116.821334A140.202667 140.202667 0 0 1 853.333333 339.313778v116.821333a46.734222 46.734222 0 0 1-93.468444 0v-116.821333c0-25.827556-20.906667-46.734222-46.734222-46.734222h-116.821334a46.734222 46.734222 0 0 1-46.734222-46.734223zM245.845333 549.546667c25.799111 0 46.734222 20.935111 46.734223 46.734222v116.821333c0 25.827556 20.906667 46.734222 46.734222 46.734222h116.821333a46.734222 46.734222 0 0 1 0 93.468445h-116.821333A140.202667 140.202667 0 0 1 199.111111 713.130667v-116.821334c0-25.799111 20.935111-46.734222 46.734222-46.734222z"
|
||||
p-id="10802"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-show="isFullscreen"
|
||||
t="1596958879235"
|
||||
class="player-controls-icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2734"
|
||||
width="32"
|
||||
height="32"
|
||||
>
|
||||
<path
|
||||
d="M0.739556 233.130667a232.391111 232.391111 0 0 1 232.391111-232.391111h557.738666a232.391111 232.391111 0 0 1 232.391111 232.391111v557.738666a232.391111 232.391111 0 0 1-232.391111 232.391111H233.130667a232.391111 232.391111 0 0 1-232.391111-232.391111V233.130667z m232.391111-139.434667a139.434667 139.434667 0 0 0-139.434667 139.434667v557.738666a139.434667 139.434667 0 0 0 139.434667 139.434667h557.738666a139.434667 139.434667 0 0 0 139.434667-139.434667V233.130667a139.434667 139.434667 0 0 0-139.434667-139.434667H233.130667z"
|
||||
p-id="2735"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path
|
||||
d="M601.088 186.652444c25.685333 0 46.506667 20.792889 46.506667 46.478223v96.796444c0 25.685333 20.792889 46.478222 46.478222 46.478222h96.796444a46.478222 46.478222 0 1 1 0 92.984889h-96.796444a139.434667 139.434667 0 0 1-139.463111-139.463111V233.130667c0-25.685333 20.821333-46.478222 46.478222-46.478223z m-414.435556 414.435556c0-25.656889 20.792889-46.478222 46.478223-46.478222h96.796444a139.434667 139.434667 0 0 1 139.463111 139.463111v96.796444a46.478222 46.478222 0 0 1-92.984889 0v-96.796444c0-25.685333-20.792889-46.478222-46.478222-46.478222H233.130667a46.478222 46.478222 0 0 1-46.478223-46.506667z"
|
||||
p-id="2736"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="player-controls-btn-hint btn-fullscreen-hint"
|
||||
>{{isFullscreen ? '退出全屏' : '进入全屏'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import volumeBar from "@/components/VideoPlayer/volume-bar.vue";
|
||||
import progressBar from "@/components/VideoPlayer/progress-bar.vue";
|
||||
import playerBarrageScreen from "@/components/VideoPlayer/player-barrage-screen.vue";
|
||||
|
||||
export default {
|
||||
name: "barrage-videoplayer",
|
||||
components: { volumeBar, progressBar, playerBarrageScreen },
|
||||
props: {
|
||||
// 倍速列表
|
||||
speedList: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return ["2.0", "1.5", "1.25", "1.0", "0.75", "0.5"];
|
||||
},
|
||||
},
|
||||
// 视频链接
|
||||
src: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
// 主题色
|
||||
primaryColor: {
|
||||
type: String,
|
||||
default: "cornflowerblue",
|
||||
},
|
||||
// 封面图的链接
|
||||
cover: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
// 弹幕的链接地址(XML文件格式,B站风格)
|
||||
biBarrageXml: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
initTime:{//默认从第几秒播放
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
isDrag:{
|
||||
type: Boolean,
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timeFastBack : 15, //单位秒,每次增减15秒
|
||||
videoDom: null, //视频dom
|
||||
isShowCover: true, // 是否显示封面
|
||||
isFullscreen: false, // 是否处于全屏模式
|
||||
isCursorStatic: false, // 鼠标是否长时间静止不动
|
||||
isMousedownProgress: false, // 鼠标是否按下了进度条(并未松开)
|
||||
isPlaying: false, // 是否正在播放
|
||||
isShowLoading: false, // 是否显示加载框
|
||||
isShowVolumeHint: false, // 是否显示音量提示条(键盘触发)
|
||||
timeoutVolumeHint: 0, // 音量提示条多久ms后隐藏
|
||||
timeoutControlsHint: 0, // 控制面板多久ms后隐藏
|
||||
currentSpeed: 1.0, // 当前倍速
|
||||
volumePercent: 100, // 当前音量百分比(0-100),仅用于文字显示
|
||||
currentVolume: 1, // 当前音量(0-1),同时作用于当前音量条的长度
|
||||
currentProgress: 0, // 当前播放进度(0-1)。同时作用于当前进度条的长度
|
||||
currentTimeFormat: "00:00:00", // 当前播放进度的文字
|
||||
fullTimeFormat: "00:00:00", // 视频总长度的文字
|
||||
barrageTimelineStart: 0, // 弹幕时间轴的起始时间点(手动调整进度条触发更新)
|
||||
isInit:false, // 是否初始化过
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
mounted() {
|
||||
this.videoDom = this.$refs.video;
|
||||
this.videoDom.focus({preventScroll: true});
|
||||
let speedValue=localStorage.getItem('boe_video_speed');
|
||||
if(speedValue){
|
||||
//console.log(speedValue,'speedValue');
|
||||
this.currentSpeed=Number(speedValue);
|
||||
this.videoDom.playbackRate=this.currentSpeed;
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
// 定时更新进度条
|
||||
if (this.isPlaying && !this.isMousedownProgress) {
|
||||
this.currentProgress =
|
||||
this.videoDom.currentTime / this.videoDom.duration;
|
||||
}
|
||||
// 定时更新进度的文字显示
|
||||
this.updateProgressText();
|
||||
// 音量提示面板的定时隐藏
|
||||
if (this.timeoutVolumeHint == null) {
|
||||
// 当定时为null,直接跳过
|
||||
} else if (this.timeoutVolumeHint === 0) {
|
||||
this.isShowVolumeHint = false;
|
||||
this.isCursorStatic = false;
|
||||
this.timeoutVolumeHint = null;
|
||||
} else if (this.timeoutVolumeHint > 0) {
|
||||
this.timeoutVolumeHint -= 1000;
|
||||
} else {
|
||||
this.timeoutVolumeHint = 0;
|
||||
}
|
||||
// 总控制面板的定时隐藏
|
||||
if (
|
||||
this.timeoutControlsHint == null ||
|
||||
this.currentProgress === 0 ||
|
||||
this.currentProgress === 1
|
||||
) {
|
||||
// 当定时为null、视频还未播放或播放完毕的时候,直接跳过
|
||||
} else if (this.timeoutControlsHint === 0) {
|
||||
this.isCursorStatic = true;
|
||||
this.timeoutControlsHint = null;
|
||||
} else if (this.timeoutControlsHint > 0) {
|
||||
this.timeoutControlsHint -= 1000;
|
||||
} else {
|
||||
this.timeoutControlsHint = 0;
|
||||
}
|
||||
// 根据视频的readyState判断下一帧是否已加载,并控制loading的显示
|
||||
this.isShowLoading = this.videoDom.readyState < 3;
|
||||
}, 1000);
|
||||
// 视频dom监听器,用于控制鼠标的显示
|
||||
this.videoDom.addEventListener("mousemove", () => {
|
||||
this.isCursorStatic = false;
|
||||
this.timeoutControlsHint = 2000;
|
||||
});
|
||||
// 监听全屏事件的变化,保存数据
|
||||
window.addEventListener("fullscreenchange", () => {
|
||||
this.isFullscreen = this.isFullScreen();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/* 切换播放状态
|
||||
*/
|
||||
togglePlayStatus() {
|
||||
if (this.videoDom.paused) {
|
||||
if(!this.isInit && this.initTime != null && this.initTime > 0){
|
||||
this.videoDom.currentTime = this.initTime;
|
||||
this.barrageTimelineStart = this.initTime;
|
||||
this.updateProgressBySetTime(this.initTime);
|
||||
this.updateProgressText();
|
||||
}
|
||||
this.videoDom.play();
|
||||
this.isPlaying = true;
|
||||
this.isInit = true;
|
||||
this.$emit('onPlayerPlay', {});//播放(播放时会调用)
|
||||
} else {
|
||||
this.videoDom.pause();
|
||||
this.isPlaying = false;
|
||||
this.$emit('onPlayerPause', {})//暂停(暂停时调用)
|
||||
}
|
||||
this.isShowCover = false;
|
||||
},
|
||||
/* 更新视频进度的文字显示
|
||||
*/
|
||||
updateProgressText() {
|
||||
this.currentTimeFormat = this.secondTimeFormat(
|
||||
this.videoDom.currentTime
|
||||
);
|
||||
this.fullTimeFormat = this.secondTimeFormat(this.videoDom.duration);
|
||||
},
|
||||
/* 时间格式化,秒格式化成xx:xx:xx
|
||||
*/
|
||||
secondTimeFormat(second) {
|
||||
let result = parseInt(second);
|
||||
let h =
|
||||
Math.floor(result / 3600) < 10
|
||||
? "0" + Math.floor(result / 3600)
|
||||
: Math.floor(result / 3600);
|
||||
let m =
|
||||
Math.floor((result / 60) % 60) < 10
|
||||
? "0" + Math.floor((result / 60) % 60)
|
||||
: Math.floor((result / 60) % 60);
|
||||
let s =
|
||||
Math.floor(result % 60) < 10
|
||||
? "0" + Math.floor(result % 60)
|
||||
: Math.floor(result % 60);
|
||||
result = `${h}:${m}:${s}`;
|
||||
return result;
|
||||
},
|
||||
/* 前进视频播放进度
|
||||
*/
|
||||
forwardCurrentTime() {
|
||||
let newCurrentTime = this.videoDom.currentTime + this.timeFastBack;
|
||||
this.videoDom.currentTime = newCurrentTime;
|
||||
this.barrageTimelineStart = newCurrentTime;
|
||||
this.updateProgressBySetTime(newCurrentTime);
|
||||
},
|
||||
/* 后退视频播放进度
|
||||
*/
|
||||
backwardCurrentTime() {
|
||||
let newCurrentTime = this.videoDom.currentTime - this.timeFastBack;
|
||||
this.videoDom.currentTime = newCurrentTime;
|
||||
this.barrageTimelineStart = newCurrentTime;
|
||||
this.updateProgressBySetTime(newCurrentTime);
|
||||
},
|
||||
/* 获取鼠标是否按下了进度条
|
||||
*/
|
||||
getMouseDownStatusOfProgressBar(value) {
|
||||
this.isMousedownProgress = value;
|
||||
},
|
||||
/* 点击进度条更新视频播放进度
|
||||
*/
|
||||
updateProgressByClickBar(value) {
|
||||
let duration = this.videoDom.duration;
|
||||
this.currentProgress = value;
|
||||
let new_current_time = Math.round(value * duration);
|
||||
this.barrageTimelineStart = new_current_time;
|
||||
this.videoDom.currentTime = new_current_time;
|
||||
},
|
||||
/* 通过新的播放时间更新视频播放进度
|
||||
*/
|
||||
updateProgressBySetTime(newCurrentTime) {
|
||||
this.currentProgress = newCurrentTime / this.videoDom.duration;
|
||||
},
|
||||
/* 提高视频音量
|
||||
*/
|
||||
increaseVolume() {
|
||||
this.isShowVolumeHint = true;
|
||||
this.timeoutVolumeHint = 2000;
|
||||
let nowVolume = this.videoDom.volume;
|
||||
if (nowVolume >= 0.9) {
|
||||
this.videoDom.volume = 1;
|
||||
this.currentVolume = 1;
|
||||
} else {
|
||||
let newVolume = this.videoDom.volume + 0.1;
|
||||
this.videoDom.volume = newVolume;
|
||||
this.currentVolume = newVolume;
|
||||
}
|
||||
},
|
||||
/* 降低视频音量
|
||||
*/
|
||||
lowerVolume() {
|
||||
this.isShowVolumeHint = true;
|
||||
this.timeoutVolumeHint = 2000;
|
||||
let nowVolume = this.videoDom.volume;
|
||||
if (nowVolume <= 0.1) {
|
||||
this.videoDom.volume = 0;
|
||||
this.currentVolume = 0;
|
||||
} else {
|
||||
let newVolume = this.videoDom.volume - 0.1;
|
||||
this.videoDom.volume = newVolume;
|
||||
this.currentVolume = newVolume;
|
||||
}
|
||||
},
|
||||
/* 视频倍速播放
|
||||
*/
|
||||
changeSpeed(e) {
|
||||
// 获取选择的倍速
|
||||
let value = e.currentTarget.dataset.value;
|
||||
localStorage.setItem('boe_video_speed',value);
|
||||
// 应用视频倍速
|
||||
this.videoDom.playbackRate = value;
|
||||
// 标记变更后的倍速,用于显示文字
|
||||
this.currentSpeed = Number(value);
|
||||
},
|
||||
/* 点击音量条后更新音量(value范围:0-1)
|
||||
*/
|
||||
updateVolumeByClickBar(value) {
|
||||
this.videoDom.volume = value;
|
||||
this.currentVolume = value;
|
||||
},
|
||||
/* 切换“全屏”和“非全屏”模式
|
||||
*/
|
||||
toggleFullScreen() {
|
||||
let element = this.$refs.area;
|
||||
if (!this.isFullScreen()) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen();
|
||||
} else if(element.webkitRequestFullscreen) {
|
||||
element.webkitRequestFullscreen();
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen();
|
||||
} else if (element.msRequestFullscreen) {
|
||||
element.msRequestFullscreen();
|
||||
} else if (element.oRequestFullscreen) {
|
||||
element.oRequestFullscreen();
|
||||
}
|
||||
this.isFullscreen = true;
|
||||
this.$emit('onFullscreen',true);//全屏
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.oRequestFullscreen) {
|
||||
document.oCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
this.isFullscreen = false;
|
||||
this.$emit('onFullscreen',false);//全屏
|
||||
}
|
||||
},
|
||||
/* 判断是否进入了全屏模式
|
||||
*/
|
||||
isFullScreen: function () {
|
||||
return !!(
|
||||
document.fullscreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen ||
|
||||
document.webkitFullScreen ||
|
||||
document.msFullScreen
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentVolume: function () {
|
||||
// 根据当前音量大小设置显示的音量文字
|
||||
this.volumePercent = Math.round(this.currentVolume * 100);
|
||||
},
|
||||
currentProgress: function () {
|
||||
// 进度条到终点时修改播放状态
|
||||
//console.log('播放中', this.videoDom.currentTime)
|
||||
this.$emit('onPlayerPlaying', this.videoDom.currentTime,this.videoDom.duration)
|
||||
if (this.currentProgress === 1) {
|
||||
this.isPlaying = false;
|
||||
this.$emit('onPlayerEnded', {})
|
||||
}
|
||||
},
|
||||
src: function () {
|
||||
// 当视频地址变更时,重载视频
|
||||
this.videoDom.load();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.player-area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cursor-lasting-static .cursor-pointer {
|
||||
cursor: none;
|
||||
}
|
||||
.cursor-lasting-static.player-controls {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
.player-video {
|
||||
outline: none;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
}
|
||||
.player-cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.player-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.player-loading img {
|
||||
fill: #fff;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
.player-paused-state {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
left: 2%;
|
||||
transform: translate(0, -100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 20px #bbbbbb;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
.player-paused-state svg {
|
||||
/* margin: 0.4rem 1rem; */
|
||||
margin: 1rem 2em 0.8em 2em;
|
||||
}
|
||||
.player-controls-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.player-volumeHint {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 12px;
|
||||
pointer-events: none;
|
||||
transform: translate(0, -100%);
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.player-volumeHint-text {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 0.4rem 0.6rem;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.player-controls {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
display: block;
|
||||
transition: visibility 0.3s, opacity 0.3s;
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
.player-controls-container:hover .player-controls {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.player-controls-container:hover .cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.player-controls-bottom {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.player-controls-bottom-left {
|
||||
padding: 0.6rem 0.4rem;
|
||||
display: inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
.player-controls-bottom-right {
|
||||
display: inline-flex;
|
||||
padding: 0.6rem 0.4rem;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
.player-controls-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color: #e5e5e5;
|
||||
padding: 0 0.4rem;
|
||||
transition: color 0.3s;
|
||||
height: 22px;
|
||||
}
|
||||
.player-controls-btn .player-controls-icon {
|
||||
height: 22px;
|
||||
transition: height 0.3s;
|
||||
}
|
||||
.player-controls-btn svg path {
|
||||
fill: #e5e5e5;
|
||||
transition: fill 0.2s;
|
||||
}
|
||||
.player-controls-btn:hover svg > path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
.player-controls-btn:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.player-controls-btn:hover .player-controls-btn-hint {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.player-controls-btn-hint {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
white-space: nowrap;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
background: rgba(21, 21, 21, 0.8);
|
||||
transition: opacity 0.3s, visibility 0s;
|
||||
transform: translate(-50%, -200%);
|
||||
}
|
||||
.btn-fullscreen-hint {
|
||||
font-size: 12px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
.player-time {
|
||||
display: inline-block;
|
||||
color: #e5e5e5;
|
||||
height: 16px;
|
||||
}
|
||||
.btn-speed:hover .speed-control {
|
||||
visibility: visible;
|
||||
}
|
||||
.speed-control {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
padding-bottom: 24px;
|
||||
transition: visibility 0.3s;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
.speed-control .speed-control-list {
|
||||
list-style: none;
|
||||
color: #e5e5e5;
|
||||
width: 50px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background: rgba(21, 21, 21, 0.8);
|
||||
}
|
||||
.speed-control .speed-control-list li {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
.speed-control .speed-control-list li:hover {
|
||||
color: #fff;
|
||||
background: rgba(99, 99, 99, 0.8);
|
||||
}
|
||||
.speed-control .speed-control-list li.current {
|
||||
color: var(--primaryColor);
|
||||
}
|
||||
.btn-volume:hover .volume-control {
|
||||
visibility: visible;
|
||||
}
|
||||
.volume-control {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
padding: 24px 10px;
|
||||
transition: visibility 0.3s;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
.volume-control-wrap {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
width: 35px;
|
||||
height: 120px;
|
||||
padding: 0.6rem 0 1rem;
|
||||
background: rgba(21, 21, 21, 0.8);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.volume-text {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@media (device-width: 100vw) {
|
||||
.player-controls-btn .player-controls-icon {
|
||||
/* height: 26px; */
|
||||
}
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
.player-loading {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
187
src/components/VideoPlayer/player-barrage-screen.vue
Normal file
187
src/components/VideoPlayer/player-barrage-screen.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div class="player-barrage-wrap">
|
||||
<div v-for="n in channelAmount" ref="channels" class="player-barrage-layer" :key="`channel-${(n -1)}`">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "player-barrage-screen",
|
||||
props: {
|
||||
// 是否正在播放
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 当前播放进度
|
||||
currentTime: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
videoDom: null,
|
||||
// 弹幕时间轴的初始时间(手动干预进度条会触发更新)
|
||||
barrageTimelineStart: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 弹幕的链接地址(XML文件格式,B站风格)
|
||||
biBarrageXml: {
|
||||
type: String,
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
barragedTag: 0, // 标记上一条弹幕的索引
|
||||
barrageList: [], // 弹幕列表
|
||||
channelAmount: 10, // 弹幕通道数量
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.requestBarrageList();
|
||||
// 测试弹幕
|
||||
setInterval(() => {
|
||||
if (this.isPlaying) {
|
||||
|
||||
let info = this.barrageList[this.barragedTag];
|
||||
if(info){
|
||||
console.log(info);
|
||||
if (info.start_time < this.videoDom.currentTime) {
|
||||
// 标记下一条弹幕的索引
|
||||
this.barragedTag++;
|
||||
// if (info.mode <= 3) {
|
||||
this.createBarrage(this.barrageList[this.barragedTag]);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
},
|
||||
methods: {
|
||||
/* 获取并处理排序弹幕列表
|
||||
*/
|
||||
requestBarrageList() {
|
||||
// 获取弹幕列表
|
||||
axios({
|
||||
method: "get",
|
||||
url: this.biBarrageXml,
|
||||
data: {},
|
||||
}).then((res) => {
|
||||
let xmlDoc = new DOMParser().parseFromString(res.data, "text/xml");
|
||||
let elements = xmlDoc.getElementsByTagName("d");
|
||||
let array = [];
|
||||
for (let n = 0, len = elements.length; n < len; n++) {
|
||||
let p_attr_list = elements[n].getAttribute("p").split(",");
|
||||
// 弹幕的信息
|
||||
let color = Number(p_attr_list[3]).toString(16);
|
||||
let info = {
|
||||
content: elements[n].innerHTML,
|
||||
start_time: Number(p_attr_list[0]),
|
||||
mode: Number(p_attr_list[1]),
|
||||
font_size: p_attr_list[2],
|
||||
font_color: color,
|
||||
timestamp: p_attr_list[4],
|
||||
barrage_pool: p_attr_list[5],
|
||||
user_hash: p_attr_list[6],
|
||||
row_id: p_attr_list[7],
|
||||
};
|
||||
array.push(info);
|
||||
}
|
||||
// 写个冒泡过渡一下。。
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
for (let j = 0, len_cache = len - i - 1; j < len_cache; j++) {
|
||||
if (array[j].start_time > array[j + 1].start_time) {
|
||||
let array_cache = array[j];
|
||||
array[j] = array[j + 1];
|
||||
array[j + 1] = array_cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.barrageList = array;
|
||||
console.log( this.barrageList);
|
||||
});
|
||||
},
|
||||
/* 新建一个弹幕
|
||||
*/
|
||||
createBarrage(info) {
|
||||
let dom = document.createElement("span");
|
||||
dom.innerText = info.content;
|
||||
// dom.setAttribute("class", "barrage");
|
||||
dom.style.animation = "barrage 5s linear 0s";
|
||||
dom.style.left = "100%";
|
||||
dom.style.fontSize = `${info.font_size}px`;
|
||||
dom.style.color = `#${info.font_color}`;
|
||||
dom.style.whiteSpace = "nowrap";
|
||||
dom.style.textShadow =
|
||||
"#000 1px 0px 1px, #000 0px 1px 1px, #000 0px -1px 1px, #000 -1px 0px 1px";
|
||||
dom.style.position = "absolute";
|
||||
//动画过渡完之后清除掉弹幕dom
|
||||
dom.addEventListener("animationend", () => {
|
||||
dom.removeEventListener("animationend", this, false);
|
||||
dom.innerHTML = "";
|
||||
dom.parentNode.removeChild(dom);
|
||||
dom.remove();
|
||||
dom = null;
|
||||
});
|
||||
this.$refs["channels"][Math.round(Math.random() * 9)].append(dom);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/* 更新弹幕
|
||||
*/
|
||||
biBarrageXml: function () {
|
||||
this.requestBarrageList();
|
||||
},
|
||||
/* 弹幕时间轴的初始时间。
|
||||
* 在手动改变进度条的时候触发,遍历弹幕列表获取下一条弹幕的索引
|
||||
*/
|
||||
barrageTimelineStart: function () {
|
||||
let list = this.barrageList;
|
||||
let barrage_start = this.barrageTimelineStart;
|
||||
for (let n = 0; n < list.length; n++) {
|
||||
if (list[n].start_time > barrage_start) {
|
||||
this.barragedTag = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.player-barrage-wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.player-barrage-layer {
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
display: block;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@keyframes barrage {
|
||||
from {
|
||||
left: 100%;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
left: 0;
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
154
src/components/VideoPlayer/progress-bar.vue
Normal file
154
src/components/VideoPlayer/progress-bar.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
$emit: updateProgress, -> 调整进度条后触发的事件,传递参数:currentProgress
|
||||
getMouseDownStatus, -> 获取是否鼠标按下的状态,传递参数:true/false
|
||||
props: width, height,
|
||||
currentProgress, -> 当前进度,范围0-1
|
||||
*/
|
||||
<template>
|
||||
<div
|
||||
class="progress-bar"
|
||||
:style="{width: width}"
|
||||
@mousedown.left="down"
|
||||
@mousemove="move"
|
||||
@mouseup="up"
|
||||
>
|
||||
<div class="progress-full" :style="{height: height}">
|
||||
<div class="progress-current" :style="{width: currentProgress * 100 + '%'}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "progress-bar",
|
||||
props: {
|
||||
width: {
|
||||
type: String,
|
||||
default: "100%",
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "4px",
|
||||
},
|
||||
// 当前进度,范围0-1
|
||||
currentProgress: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isDrag:{
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
is_mousedown_progress: false, //是否点击了进度条并保持点击状态
|
||||
current_width_px: 0, // 当前进度条的像素长度
|
||||
init_clientX: 0, //初始进度的偏移量,相对于视口
|
||||
dom_full: null, //完整进度条的dom,即进度条背景
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.isDrag,'isDrag');
|
||||
//初始化一些固定数据
|
||||
let dom_full = this.$el.getElementsByClassName("progress-full")[0];
|
||||
this.dom_full = dom_full;
|
||||
//绑定全局监听器
|
||||
window.addEventListener("mousemove", (e) => {
|
||||
if (this.is_mousedown_progress) {
|
||||
this.move(e);
|
||||
}
|
||||
});
|
||||
window.addEventListener("mouseup", (e) => {
|
||||
this.up(e);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
//
|
||||
},
|
||||
methods: {
|
||||
down(e) {
|
||||
if(this.isDrag) {
|
||||
this.$emit("getMouseDownStatus", true);
|
||||
this.is_mousedown_progress = true;
|
||||
// 获取完整进度条的clientX(dom左上角)
|
||||
let init_clientX = this.dom_full.getBoundingClientRect().left;
|
||||
// 计算调整后的当前进度条的长度
|
||||
this.current_width_px = e.clientX - init_clientX;
|
||||
// 设置当前的播放进度(同时作用于当前进度条的样式)
|
||||
let current =
|
||||
(e.clientX - init_clientX) / this.dom_full.clientWidth;
|
||||
this.$emit("updateProgress", current);
|
||||
}
|
||||
|
||||
},
|
||||
move(e) {
|
||||
if (this.is_mousedown_progress && this.isDrag) {
|
||||
let init_clientX = this.dom_full.getBoundingClientRect().left;
|
||||
this.current_width_px = e.clientX - init_clientX;
|
||||
let current =
|
||||
(e.clientX - init_clientX) / this.dom_full.clientWidth;
|
||||
this.$emit("updateProgress", current);
|
||||
}
|
||||
},
|
||||
up() {
|
||||
if (this.is_mousedown_progress && this.isDrag) {
|
||||
// 标记鼠标不处于按下的状态了
|
||||
this.is_mousedown_progress = false;
|
||||
// 松开鼠标后,即调整进度条后,此时的进度(0-1)
|
||||
let current =
|
||||
this.current_width_px / this.dom_full.clientWidth;
|
||||
this.$emit("updateProgress", current);
|
||||
this.$emit("getMouseDownStatus", false);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 2px 0;
|
||||
transition: height 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-bar:hover .progress-full {
|
||||
height: 6px !important;
|
||||
}
|
||||
.progress-full {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background: rgb(201, 201, 201);
|
||||
transition: height 0.3s;
|
||||
}
|
||||
.progress-current {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: inherit;
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
.progress-current::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
bottom: 50%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 100%;
|
||||
background: #fff;
|
||||
transform: translate(50%, -50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.progress-bar:hover .progress-current::after {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
130
src/components/VideoPlayer/volume-bar.vue
Normal file
130
src/components/VideoPlayer/volume-bar.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
$emit: updateVolume -> 更新音量,参数为一个0-1的音量值
|
||||
props: width, height,
|
||||
currentVolume, -> 当前音量(0-1)
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="volume-bar"
|
||||
:style="{width: '100%', height: height}"
|
||||
@mousedown.left.stop="down"
|
||||
@mousemove.stop="move"
|
||||
@mouseup.stop="up"
|
||||
>
|
||||
<div class="volume-full" :style="{width: width, height: height}">
|
||||
<div
|
||||
class="volume-current volume-ball"
|
||||
:style="{width: '100%', height: currentVolume * 100 + '%'}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "volumeBar",
|
||||
props: {
|
||||
width: {
|
||||
type: String,
|
||||
default: "100%",
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "100%",
|
||||
},
|
||||
//当前音量(0-1)
|
||||
currentVolume: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
is_click_bar: false, // 是否点击了音量条
|
||||
full_height: null, //音量条的总高度(像素)
|
||||
dom_volume_full: null, //总音量条的dom
|
||||
full_bar_client_top: 0, //满载的音量条相对于视口的高度
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.dom_volume_full = this.$el.getElementsByClassName("volume-full")[0];
|
||||
window.addEventListener("mousemove", (e) => {
|
||||
if (this.is_click_bar) {
|
||||
this.move(e);
|
||||
}
|
||||
});
|
||||
window.addEventListener("mouseup", (e) => {
|
||||
this.up(e);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
down(e) {
|
||||
this.is_click_bar = true;
|
||||
//赋值高度备用
|
||||
let full_height = this.dom_volume_full.clientHeight;
|
||||
this.full_height = full_height;
|
||||
this.$emit('updateVolume', 1 - e.offsetY / this.full_height)
|
||||
},
|
||||
move(e) {
|
||||
//满载的音量条相对于视口的高度
|
||||
let full_bar_client_top = this.dom_volume_full.getBoundingClientRect().top;
|
||||
this.full_bar_client_top = full_bar_client_top;
|
||||
//当前点击位置距离视口的高度
|
||||
let clickY = e.clientY;
|
||||
//偏移量
|
||||
let offsetY = clickY - full_bar_client_top;
|
||||
//当鼠标按下的时候才进行更新数据
|
||||
if (this.is_click_bar ) {
|
||||
let new_volume = 1 - (offsetY / this.full_height);
|
||||
if (new_volume > 1) {
|
||||
this.$emit("updateVolume", 1)
|
||||
} else if (new_volume < 0) {
|
||||
this.$emit("updateVolume", 0)
|
||||
} else {
|
||||
this.$emit("updateVolume", new_volume)
|
||||
}
|
||||
}
|
||||
},
|
||||
up() {
|
||||
this.is_click_bar = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.volume-bar {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.volume-full {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background: rgb(180, 180, 180);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.volume-current {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-color: var(--primaryColor);
|
||||
pointer-events: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.volume-current::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
/* top: 0; */
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 100%;
|
||||
background: #fff;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user