刚开始在网上下了点视频教程想着用些电脑自带的播放器。后来级数太多了,操作不方便。就开始自己捣鼓了。
痛点是是视频教程本身带来的,不方便反复的找重点。过去了不好快退等。
就基于video.js,设计了各种功能,鼠标移到上方显示
播放、暂停、静音、1倍速、1.5倍速、2倍速、⏪5秒、⏩5秒、上一节、下一节
现在做了分享。
在vue2项目中建个 list.vue 文件
html
<template>
<div class="zoo-common-layout">
<!-- 右侧的视频模块 -->
<div class="zoo-common-left">
<my-player v-if="cUrl" :video-src="cUrl" @changeIndex="changeActiveIndex"></my-player>
</div>
<!-- 右侧的视频模块 -->
<div class="zoo-common-right" :class="{'show': isShow}">
<div class="drag-mian">
<div class="drag-title">
<i class="el-icon el-icon-arrow-left" v-if="isShow == false" @click="isShow = true"></i>
<i class="el-icon el-icon-arrow-right" v-if="isShow == true" @click="isShow = false"></i>
<span v-if="isShow == true">
列表
</span>
</div>
<div class="drag-search-box" v-if="isShow == true">
<el-input placeholder="输入关键字" v-model="filterText" suffix-icon="el-icon-search" clearable />
</div>
<el-scrollbar class="drag-tree-scrollbar" v-loading="treeLoading">
<el-tree
v-show = "refreshTree && isShow == true"
ref="treeBox"
:data="treeData"
:props="defaultProps"
highlight-current
:expand-on-click-node="false"
node-key="id"
@node-click="handleNodeClick"
class="JNPF-common-el-tree"
:filter-node-method="filterNode"
>
<span
class="custom-tree-node"
slot-scope="{ data, node }"
:title="data.fullName"
:class="{'marquee': true}"
>
<i>{{ node.id }} </i>
<span class="text" :title="data.fullName">{{ node.label }}</span>
</span>
</el-tree>
<div v-if ="isShow == false">
<span v-for="(node, index) in treeData" :key="node.id">
<el-tag :class="{'animated-border-box': index == activeIndex }" @click="handleNodeClick(node)">{{ index + 1 }}</el-tag>
</span>
</div>
</el-scrollbar>
</div>
</div>
</div>
</template>
<script>
import list from './list-data'
import MyPlayer from './video006-c.vue'
export default {
name: 'Video006Component',
components: {
MyPlayer
},
data() {
return {
files: list,
active: list[0],
activeIndex: 0,
cUrl: null,
isShow: true,
treeLoading: false,
filterText: "",
parentId: null,
organizeId: "",
treeData: [],
defaultProps: {
// id:'id',
children: "children",
label: "fullName",
},
expands: true,
// 默认选中的节点key值
defaultCheckedKeys: [1],
refreshTree: true,
isFullscreen: false,
}
},
watch: {
active(val) {
this.cUrl = './' + val.parentNames.replace(" ", "")
},
filterText(val) {
this.$refs.treeBox.filter(val);
},
},
mounted() {
this.cUrl = './' + this.active.parentNames.replace(" ", "");
this.treeData = this.files.map((item, index) => {
return {
...item,
id: index + 1,
num: index + 1,
fullName: item.name,
children: null
};
});
console.log('this.treeData', this.treeData);
const node = this.$refs.treeBox.getNode(1);
if (node) {
node.setChecked(true);
}
},
methods: {
changeActive(item, index) {
this.active = { ...item };
this.activeIndex = index;
},
changeActiveIndex(sign) {
if (sign === 'prev') {
if (this.activeIndex === 0) {
this.activeIndex = this.files.length - 1;
} else {
this.activeIndex -= 1;
}
} else {
if (this.activeIndex === this.files.length - 1) {
this.activeIndex = 0;
} else {
this.activeIndex += 1;
}
}
let item = this.files[this.activeIndex];
this.active = { ...item }
},
handleNodeClick(data) {
console.log(data);
if (this.organizeId === data.num) return;
this.organizeId = data.num;
this.active = { ...data };
this.activeIndex = data.num - 1;
console.log('data', data);
let id = data.id;
// 清除所有选中的节点 is-current
this.$refs.treeBox.setCheckedNodes([]); // 清空所有选中的节点
let node = this.$refs.treeBox.getNode(id);
console.log('node', node);
if (node) {
this.$refs.treeBox.setCurrentNode(node);
}
},
filterNode(value, data) {
if (!value) return true;
return data.fullName.indexOf(value) !== -1;
},
screenfullchange(isFullscreen) {
this.isFullscreen = isFullscreen;
console.log('isFullscreen', isFullscreen);
},
},
}
</script>
<style lang="scss" scoped>
.zoo-common-layout {
position: relative;
height: 100%;
width: 100%;
background-color: skyblue;
display: flex;
padding: 5px;
}
.zoo-common-left {
height: 100%;
flex: 1;
margin-right: 12px;
}
.zoo-common-right {
width: 64px;
height: 100%;
border-left: 1px solid #FF00FF;
}
.zoo-common-right.show {
width: 240px;
height: 100%;
}
.drag-mian {
height: 100%;
width: 100%;
background-color: #FFF;
border-radius: 5px;
}
.drag-title {
font-size: 14px;
font-weight: bold;
text-align: center;
width: 100%;
height: 40px;
line-height: 40px;
position: relative;
}
.drag-title i {
font-size: 24px;
font-weight: bold;
text-align: center;
color: #2926e9;
cursor: pointer;
position: absolute;
left: 5px;
top: 5px;
}
.drag-search-box {
padding: 5px 12px;
}
.drag-tree-scrollbar {
height: calc(100% - 80px);
overflow-y: auto;
}
.relation-graph-main {
width: calc(100% - 220px);
height: calc(100vh - 20px);
margin: 10px;
margin-right: 200px;
}
.drag-mian .el-tag {
width: 60px;
text-align: center;
margin: 3px auto;
cursor: pointer;
}
.animated-border-box {
font-size: 16px;
background-color: rgb(80, 98, 150);
background: url(../assets/images/media-player/btn-active.png) center no-repeat;
background-size: 80px 80px;
color: #FFF;
}
::v-deep .el-tree .el-tree-node__content{
height: 32px;
}
::v-deep .el-tree .is-checked{
background-color: skyblue;
color: #FFF;
}
::v-deep .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: skyblue;
color: #FFF;
}
.custom-tree-node{
width: 200px;
text-align: left;
}
/* 树节点文字的跑马灯效果 */
.marquee {
display: inline-block;
width: 200px; /* 设置容器的宽度 */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: middle;
position: relative;
}
.marquee .text {
display: inline-block;
white-space: nowrap;
// animation: marquee 10s linear infinite;
width: 160px; /* 设置容器的宽度 */
overflow: hidden;
white-space: nowrap;
}
/* 跑马灯动画 */
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
</style>
然后 再建一个 video006-c.vue 作为视频播放组件。
html
<template>
<div class="zoo-w-wrapper">
<div class="v-options">
<div class="v-c-options">
<!-- 控制按钮 -->
<div class="option-btn" @click="videoPlay">播放</div>
<div class="option-btn" @click="videoPause">暂停</div>
<div class="option-btn" @click="videoMute">静音</div>
<div class="option-btn" :class="{ 'active': speed === 1 }" @click="videoDoubleSpeed(1)">1倍速</div>
<div class="option-btn" :class="{ 'active': speed === 1.5 }" @click="videoDoubleSpeed(1.5)">1.5倍速</div>
<div class="option-btn" :class="{ 'active': speed === 2 }" @click="videoDoubleSpeed(2)">2倍速</div>
<!-- 5秒跳跃 -->
<div class="option-btn" @click="skipTime(-5)">⏪5秒</div>
<div class="option-btn" @click="skipTime(5)">⏩5秒</div>
<!-- 上一节、下一节 -->
<div class="option-btn" @click="changeActiveIndex('prev')">上一节</div>
<div class="option-btn" @click="changeActiveIndex('next')">下一节</div>
</div>
</div>
<!-- 视频播放器 -->
<video ref="videoPlayer" class="video-js vjs-default-skin" controls>
<source :src="videoSrc" type="video/mp4" />
</video>
</div>
</template>
<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
export default {
props: {
videoSrc: {
type: String,
required: true,
},
},
data() {
return {
player: null, // video.js 实例
speed: 1, // 当前播放速度
};
},
mounted() {
// 初始化 video.js 播放器
this.player = videojs(this.$refs.videoPlayer);
// 监听播放器的播放事件来更新播放状态
this.player.on('play', () => {
console.log('Video is playing');
});
// 监听播放器的暂停事件
this.player.on('pause', () => {
console.log('Video is paused');
});
},
watch: {
// 监听 videoSrc 的变化
videoSrc(newSrc) {
if (this.player) {
this.player.src({ type: 'video/mp4', src: newSrc });
this.player.load(); // 强制加载新的源
this.player.play(); // 自动播放新的源(可选)
this.videoDoubleSpeed(2);
}
}
},
beforeDestroy() {
// 清理 video.js 实例
if (this.player) {
this.player.dispose();
}
},
methods: {
// 播放视频
videoPlay() {
if (this.player) {
this.player.play();
}
},
// 暂停视频
videoPause() {
if (this.player) {
this.player.pause();
}
},
// 静音/取消静音
videoMute() {
if (this.player) {
const isMuted = this.player.muted();
this.player.muted(!isMuted); // 切换静音状态
}
},
// 设置视频播放速度
videoDoubleSpeed(newSpeed) {
if (this.player) {
this.speed = newSpeed;
this.player.playbackRate(newSpeed);
}
},
// 跳跃时间
skipTime(seconds) {
if (this.player) {
const currentTime = this.player.currentTime();
this.player.currentTime(currentTime + seconds);
}
},
// 上一节/下一节事件(调用父组件的方法)
changeActiveIndex(direction) {
this.$emit('changeIndex', direction); // 向父组件传递事件
}
}
};
</script>
<style lang="scss" scoped>
.zoo-w-wrapper{
width: 100%;
height: 100%;
position: relative;
}
.v-options{
position: absolute;
left: 0;
top: 0;
height: 96px;
width: 100%;
background-color: rgba($color: #24c46c, $alpha: .1);
z-index: 99;
cursor: pointer;
.v-c-options{
display: none;
height: 60px;
width: 100%;
line-height: 60px;
background: linear-gradient(to right, #3fbfdf, #650472, #146809, #2600ff);
// background: #2600ff;
position: relative;
z-index: 999;
}
}
.v-options:hover .v-c-options{
display: flex;
}
.v-c-options{
.option-btn {
width: 88px;
height: 32px !important;
line-height: 32px;
background: url(../assets/images/media-player/btn-def.png) center no-repeat;
background-size: 100%;
color: #fff;
cursor: pointer;
margin: 16px 5px 0;
text-align: center;
box-shadow: 0 0 1px 1px #b4e6ff;
}
.option-btn:hover {
background-color: rgb(80, 98, 150);
background: url(../assets/images/media-player/btn-active.png) center no-repeat;
background-size: 100%;
}
.option-btn.active {
width: 88px;
height: 32px !important;
line-height: 32px;
background: url(../assets/images/media-player/btn-active.png) center no-repeat;
background-size: 100%;
color: #fff;
cursor: pointer;
margin: 16px 5px 0;
text-align: center;
box-shadow: 0 0 1px 1px #b4e6ff;
}
.option-btn.success {
width: 150px;
background: url(../assets/images/media-player/btn-green.png) center no-repeat;
background-size: 100%;
cursor: default;
margin-right: 12px;
margin-left: 12px;
}
.option-btn.success:hover {
background: url(../assets/images/media-player/btn-green.png) center no-repeat;
background-size: 100%;
cursor: default;
}
.active-course {
color: #FFF;
flex-direction: row;
display: flex;
justify-content: flex-start;
.success {
width: 100px;
background: url(../assets/images/media-player/btn-green.png) center no-repeat;
background-size: 100%;
cursor: default;
margin-right: 12px;
margin-left: 12px;
}
.success:hover {
background: url(../assets/images/media-player/btn-green.png) center no-repeat;
cursor: default;
}
}
}
.moveCotr.right,
.moveCotr.left {
width: 32px;
height: 64px;
border-radius: 100%;
cursor: pointer;
position: absolute;
left: 0;
top: 50%;
margin-left: -32px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin-top: -32px;
img {
opacity: 0.6;
}
img:hover {
opacity: 0.8;
}
}
.video-js{
width: 100%;
height: calc(100vh - 32px);
margin-top: 12px;
}
</style>
另外有个 list-data.js 文件 作文存放 列表数据的
html
const list = [
{
name: '.-S07-01.mp4',
type: 'file',
parentNames: 'wwd第7季25集/.-S07-01.mp4',
realName: '.mp4'
},
{
name: '.-S07-02.mp4',
type: 'file',
parentNames: 'wwd第7季25集/.-S07-02.mp4',
realName: '.mp4'
}
]
export default list;
这里的 资源放到 public 下
然后修改对应信息 就可以正常播放了。
另外会有些图片会报错,自己解决下哈。或者si我 。
祝顺利体验。