一、效果展示
二、参考链接
easyPlayer: https://github.com/tsingsee/EasyPlayer.js
grid:https://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html
vue-fullscreen:https://mirari.cc/2017/08/14/%E5%85%A8%E5%B1%8F%E5%88%87%E6%8D%A2%E7%BB%84%E4%BB%B6vue-fullscreen/
三、准备工作
注 如果easyPlayer不可用,可以使用livePlayer,
参考链接https://www.liveqing.com/docs/manuals/LivePlayer.html#%E5%B1%9E%E6%80%A7-property
-
安装 @easydarwin/easyplayer 和 vue-fullscreen
-
在 main.js 中全局注册 vue-fullscreen
import fullscreen from 'vue-fullscreen' Vue.use(fullscreen)
-
下载 EasyPlayer-element.min.js 放在根目录的 public 文件夹下,在 index.html 中引入
四、 代码实现
1. src/views/monitor.vue
<template>
<div class="container">
<div class="layout-main-sidebar">
<TreeList @clickInfo="onClickInfo" />
</div>
<div class="layout-main-container">
<div class="monitor-screen-top">
<div
v-for="(item1, index1) in layoutPlayerList"
:key="index1"
:class="[
'layout-screen-btn',
item1.containerClass,
{ active: item1.id === screenTypeActive }
]"
@click="handleScreenType(item1)"
>
<div
v-for="(item2, index2) in item1.children"
:key="index2"
:class="item2.itemClass"
></div>
</div>
<i class="el-icon-full-screen" @click="toggle()"></i>
</div>
<fullscreen
:fullscreen.sync="fullscreen"
class="monitor-screen-container"
>
<div :class="['layout-screen-container', playList.containerClass]">
<div
v-for="(item, index) in playList.children"
:key="index"
:class="item.itemClass"
@click="onActiveIndex(index + 1)"
>
<Player
:active-index="activeIndex"
:index="index + 1"
:id="item.id"
:title="item.title"
:url="item.url"
@close="onClose"
/>
</div>
</div>
</fullscreen>
</div>
</div>
</template>
<script>
import TreeList from '@/components/TreeList.vue'
import Player from '@/components/Player.vue'
export default {
name: 'Monitor',
components: {
Player,
TreeList
},
data() {
return {
screenTypeActive: 3, // 当前显示模式
activeNum: 6, // 显示数量
fullscreen: false, // 是否全屏
// 当前播放对象
playList: {
id: 3,
type: 6,
containerClass: 'layout-screen_3',
children: [
{ id: '', title: '', url: '', itemClass: 'screen-item_6-1' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' }
]
},
// 预定义所有对象
layoutPlayerList: [
{
id: 1,
type: 1,
containerClass: 'layout-screen_1',
children: [{ id: '', title: '', url: '' }]
},
{
id: 2,
type: 4,
containerClass: 'layout-screen_2',
children: [
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' }
]
},
{
id: 3,
type: 6,
containerClass: 'layout-screen_3',
children: [
{ id: '', title: '', url: '', itemClass: 'screen-item_6-1' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' }
]
},
{
id: 5,
type: 9,
containerClass: 'layout-screen_3',
children: [
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' }
]
},
{
id: 9,
type: 16,
containerClass: 'layout-screen_4',
children: [
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' },
{ id: '', title: '', url: '' }
]
}
],
activeIndex: 0 // 当前激活播放器
}
},
methods: {
// 点击头部按钮,如果点击的按钮,是当前显示的模式 return
// 否则获取当前点击的 id,根据 id 匹配到预定义的数据,赋值给 playList
// 设置当前显示模式
handleScreenType(item) {
if (this.screenTypeActive === item.id) return
this.screenTypeActive = item.id
const data = this.layoutPlayerList.find(
item => item.id === this.screenTypeActive
)
this.playList = data
this.activeNum = item.type
},
// 是否全屏显示
toggle() {
this.fullscreen = !this.fullscreen
},
// TreeList 组件传递过来的方法,点击获取节点数据
// activeIndex 为 0 表示,没有预选播放位置
// 设置当前播放对象的 id,title,url 等需要配置的属性,传递给 Player 组件
// 因为 activeIndex 从 0 开始 ,如果 activeIndex >= activeNum 表示已经到了最后一个
// 否则 activeIndex ++ ,播放位置到下一个
onClickInfo(val) {
if (this.activeIndex === 0) this.activeIndex = 1
this.playList.children[this.activeIndex - 1].id = this.activeIndex + ''
this.playList.children[this.activeIndex - 1].title = val.name
this.playList.children[this.activeIndex - 1].url = val.url
if (this.activeIndex >= this.activeNum) {
this.activeIndex = 1
} else {
this.activeIndex++
}
},
// 设置 activeIndex 为当前播放的 index
// 目的:高亮显示容器、控制播放位置
onActiveIndex(index) {
this.activeIndex = index
},
// Player 组件传递过来的方法,点击关闭视频
onClose(val) {
this.playList.children[val - 1].id = ''
this.playList.children[val - 1].title = ''
this.playList.children[val - 1].url = ''
}
}
}
</script>
<style lang="less" scoped>
.container {
display: flex;
height: 100%;
}
</style>
2. src/style/monitor.less
.layout-main-sidebar {
width: 15%;
background-color: #ccc;
}
.layout-main-container {
width: 85%;
.monitor-screen-top {
background-color: chocolate;
display: flex;
.layout-screen-btn {
display: grid;
width: 20px;
height: 20px;
margin: 10px 5px;
cursor: pointer;
div {
height: 100%;
border: 1px solid #333;
background-color: white;
}
}
.active {
div {
background-color: deepskyblue;
}
}
.el-icon-full-screen {
color: #fff;
font-size: 23px;
display: flex;
align-items: center;
margin-left: 5px;
cursor: pointer;
}
}
.monitor-screen-container {
height: calc(100% - 40px);
.layout-screen-container {
width: 100%;
height: 100%;
display: grid;
// > div 父子级关系只能一级
> div {
height: 100%;
}
.player-container {
position: relative; // 如果不设置成 relative,会显示一个全屏窗口
height: 100%;
border: 1px solid red;
box-sizing: border-box;
.player-title {
position: absolute;
top: 0;
left: 0;
z-index: 1;
color: #fff;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
display: flex;
justify-content: space-between;
padding: 5px 10px;
box-sizing: border-box;
.el-icon-close {
cursor: pointer;
}
}
}
.active-index {
box-shadow: 0 0 10px 1px deepskyblue;
border: 1px solid deepskyblue;
z-index: 9999;
}
}
}
}
// .layout-screen_1 {
// }
.layout-screen_2 {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
.layout-screen_3 {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
}
.layout-screen_4 {
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
}
.screen-item_6-1 {
grid-row: 1 / 3;
grid-column: 1 / 3;
}
3. src/components/Player.vue
<template>
<!-- 根据 monitor 父组件传递过来的数据,判断容器是否需要高亮显示 -->
<div :class="['player-container', { 'active-index': activeIndex === index }]">
<div class="player-title" v-if="id !== ''">
<span> {{ title }}</span>
<i class="el-icon-close" @click="handleClose" />
</div>
<EasyPlayer
:videoUrl="url"
live
:fluent="fluent"
:autoplay="autoplay"
:aspect="aspect"
stretch
></EasyPlayer>
</div>
</template>
<script>
import EasyPlayer from '@easydarwin/easyplayer'
export default {
name: 'Player',
components: { EasyPlayer },
props: {
activeIndex: {
type: Number,
default: 0
},
index: {
type: Number,
default: 0
},
id: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
url: {
type: String,
default: ''
}
},
data() {
return {
aspect: '100%',
fluent: true,
autoplay: true
}
},
methods: {
handleClose() {
this.$emit('close', this.index)
}
}
}
</script>
<style></style>
4. src/components/TreeList.vue
<template>
<div>
<el-tree
:data="data"
:props="defaultProps"
@node-click="handleNodeClick"
></el-tree>
</div>
</template>
<script>
export default {
data() {
return {
data: [
{
label: 'test1',
children: [
{
label: 'test1',
name: 'test1',
url: 'http://cctvalih5ca.v.myalicdn.com/live/cctv1_2/index.m3u8'
}
]
},
{
label: 'test2',
children: [
{
label: 'test2',
name: 'test2',
url: 'http://220.161.87.62:8800/hls/1/index.m3u8'
}
]
}
],
defaultProps: {
children: 'children',
label: 'label'
}
}
},
methods: {
handleNodeClick(data) {
this.$emit('clickInfo', data)
}
}
}
</script>