回放video 加 自定义tabBar 进度条
<template>
<!-- 回放 -->
<div class='playback' ref="playback">
<div class="video-wrapper">
<video-player
class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
crossOrigin="anonymous"
style="height: 100%;"
autoplay
:options="playerOptions"
@timeupdate="onPlayerTimeupdate($event)"
@ended="onPlayerEnded($event)"
@ready="playerReadied"
@pause="onPlayerPause($event)"
@playing="onPlayerPlaying($event)"
>
<span>1</span>
</video-player>
<div class="bottom-controls">
<!-- 进度条 -->
<div class="ruler-wrapper hide" ref="rulerWrapper">
<div class="ruler-wrap">
<div class="left" @click="swiperLeft"><i class="el-icon-arrow-left"></i></div>
<div class="ruler-content-wrap">
<div class="ruler-content-box">
<div class="ruler-content" ref="content">
<div class="ruler-item" ref="item">
<div class="ruler-lists" v-for="(item, index) in 288" :key="index">
<span v-if="index%6===0">{{ _formatSeconds(index/6*30) }}</span>
</div>
</div>
<div class="drop1" v-for="(item, index) in drops" :key="'k1'+index" :style="[{width: item.width+'px'}, {left: item.position+'px'}]"></div>
<div class="drop" ref="drops" v-for="(item, index) in drops" :key="'k2'+index" :class="{'red': item.status}" :style="[{width: item.width+'px'}, {left: item.position+'px'}]" @click="videoClick($event, index)"></div>
</div>
</div>
<div class="currTime" ref="currTime"></div>
</div>
<div class="right" @click="swiperRight"><i class="el-icon-arrow-right"></i></div>
</div>
</div>
<!-- 底部控制菜单 -->
<div class="control-wrapper">
<!-- 左边 -->
<div class="pic" @click="dianj">
<el-image
:url="dialogImageUrl.url"
:src="dialogImageUrl.url"
fit="cover"
></el-image>
</div>
<!-- 右边 -->
<div class="bottomRight">
<!-- 音量 -->
<span class="urlRight">
<el-image
:url="songS.url"
@click="yingliang"
:src="songS.url"
fit="cover"
></el-image>
<el-slider
class="pr"
:show-tooltip="false"
v-model="songsss"
vertical
height="100px"
v-show="yl"
@input="volumeChange"
>
</el-slider>
</span>
<!-- 截屏 -->
<span download id="downLoadImg" class="urlRight">
<el-image
@click="getVideoPic"
:url="jieP.url"
:src="jieP.url"
fit="cover"
></el-image>
</span>
<!-- 全屏 -->
<span @click="fullScreenHandle" class="urlRight">
<el-image
:url="qScreen.url"
:src="qScreen.url"
fit="cover"
></el-image>
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { videoPlayer } from "vue-video-player";
import "video.js/dist/video-js.css";
export default {
components: {
videoPlayer,
},
props: {
domLength: { // 视频个数
type: Number,
default: 1
},
// devIds: { // 设备id
// type: String,
// default: "1"
// },
// dayDate: { //日期
// type: String,
// default: "2021-03-19"
// },
address: {
type: Array
}
},
data() {
return {
jieP: {
url: require("@/assets/playback/jp.png"),
flg: 1,
},
songS: {
url: require("@/assets/playback/song.png"),
flg: 1,
},
songS2: {
url: require("@/assets/playback/song.png"),
flg: 1,
},
songS1: {
url: require("@/assets/playback/songN.png"),
flg: 2,
},
qScreen: {
url: require("@/assets/playback/qScreen.png"),
flg: 1,
},
dialogImageUrl: {
//播放按钮
url: require("@/assets/playback/bf.png"),
flg: 1,
},
dialogImageUrl1: {
url: require("@/assets/playback/bf.png"),
flg: 2,
},
dialogImageUrl2: {
url: require("@/assets/playback/zd.png"),
flg: 2,
},
songsss: 1,
yl: false,
currentVideo: 0,
playerOptions: {
controls: false, // 是否显示控制栏
playbackRates: [0.5, 1.0, 1.5, 2.0], //播放速度
autoplay: false, //如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: "zh-CN",
aspectRatio: "16:10", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [
{
// type: "video/mp4",
// src: "https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm", // url地址
type: "application/x-mpegURL", // 这里的种类支持很多种:基本视频格式、直播、流媒体等,具体可以参看git网址项目
// src: "http://live.chci.cn/20210324/ANScHyUN3LcvGDPDuFbjiB/ANScHyUN3LcvGDPDuFbjiB.m3u8", //你的m3u8地址(必填)
}
],
// poster: "poster.jpg", //你的封面地址
notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true, //当前时间和持续时间的分隔符
durationDisplay: true, //显示持续时间
remainingTimeDisplay: true, //是否显示剩余时间功能
fullscreenToggle: true, //全屏按钮
}
},
videos: [
// {
// id: 1,
// startDateStr: "00:05:00",
// endDateStr: "00:15:53",
// historyUrl: "https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm",
// status: 0
// },
],
drops: []
}
},
created() {
this.videos = this.address
this.videoSwitch(true)
this.resetControlStyle(this.domLength)
},
mounted() {
this.domOnLoad()
// const _this = this
// window.onresize = function() {
// console.log(_this.$refs.videoPlayer.player.isFullscreen())
// }
},
watch: {
domLength(val) {
this.resetControlStyle(val)
},
address(val) {
this.videos = val
this.videoSwitch(true)
},
},
methods: {
getVideoRecorders() {
let params = {
SSOID: this.$store.state.cookie,
dayDate: this.dayDate,
devIds: this.devIds
}
this.$axios
.get("/apis/monitorcenter/device/getRecorders", {
params,
}
)
.then((res) => {
res.data.result[0].deviceRecorderList.map(res=>{
console.log(
res.historyUrl
);
})
console.log(res);
})
.catch(() => {
this.$message({
type: "info",
message: "视频加载失败 请重试!",
});
});
},
resetControlStyle(domLength) {
let root = document.querySelector(":root")
switch (domLength) {
case 1:
root.style.setProperty("--ruler-height", "100px")
root.style.setProperty("--ruler-pad-top", "29px")
root.style.setProperty("--ruler-big-line", "50px")
root.style.setProperty("--ruler-small-line", "18px")
root.style.setProperty("--ruler-font-size", "24px")
root.style.setProperty("--ruler-font-top", "29px")
root.style.setProperty("--ruler-swiper-size", "30px")
root.style.setProperty("--ruler-currTime-top", "-49px")
root.style.setProperty("--ruler-currTime-width", "40px")
root.style.setProperty("--ruler-currTime-height", "163px")
root.style.setProperty("--control-height", "90px")
root.style.setProperty("--control-play-size", "50px")
root.style.setProperty("--control-icon-size", "28px")
break;
case 4:
root.style.setProperty("--ruler-height", "46px")
root.style.setProperty("--ruler-pad-top", "13px")
root.style.setProperty("--ruler-big-line", "24px")
root.style.setProperty("--ruler-small-line", "8px")
root.style.setProperty("--ruler-font-size", "12px")
root.style.setProperty("--ruler-font-top", "13px")
root.style.setProperty("--ruler-swiper-size", "16px")
root.style.setProperty("--ruler-currTime-top", "-21px")
root.style.setProperty("--ruler-currTime-width", "18px")
root.style.setProperty("--ruler-currTime-height", "74px")
root.style.setProperty("--control-height", "40px")
root.style.setProperty("--control-play-size", "24px")
root.style.setProperty("--control-icon-size", "12px")
break;
case 9:
root.style.setProperty("--ruler-height", "30px")
root.style.setProperty("--ruler-pad-top", "10px")
root.style.setProperty("--ruler-big-line", "15px")
root.style.setProperty("--ruler-small-line", "5px")
root.style.setProperty("--ruler-font-size", "7px")
root.style.setProperty("--ruler-font-top", "8px")
root.style.setProperty("--ruler-swiper-size", "12px")
root.style.setProperty("--ruler-currTime-top", "-14px")
root.style.setProperty("--ruler-currTime-width", "12px")
root.style.setProperty("--ruler-currTime-height", "49px")
root.style.setProperty("--control-height", "26px")
root.style.setProperty("--control-play-size", "16px")
root.style.setProperty("--control-icon-size", "10px")
break;
case 16:
root.style.setProperty("--ruler-height", "22px")
root.style.setProperty("--ruler-pad-top", "7px")
root.style.setProperty("--ruler-big-line", "12px")
root.style.setProperty("--ruler-small-line", "4px")
root.style.setProperty("--ruler-font-size", "4px")
root.style.setProperty("--ruler-font-top", "6px")
root.style.setProperty("--ruler-swiper-size", "10px")
root.style.setProperty("--ruler-currTime-top", "-11px")
root.style.setProperty("--ruler-currTime-width", "8px")
root.style.setProperty("--ruler-currTime-height", "37px")
root.style.setProperty("--control-height", "20px")
root.style.setProperty("--control-play-size", "12px")
root.style.setProperty("--control-icon-size", "8px")
break
}
},
// 截屏
DPR() {
// 获取设备dpi
if (window.devicePixelRatio && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
}
return 1;
},
getVideoPic() {
let video = document.getElementsByClassName("vjs-tech")[0];
const scaleBy = this.DPR();
let canvas = document.createElement("canvas");
let w = window.innerWidth;
let h = (window.innerWidth / 16) * 9;
canvas.width = w * scaleBy;
canvas.height = h * scaleBy;
const ctx = canvas.getContext("2d");
ctx.drawImage(video, 0, 0, w, h);
ctx.drawImage(video, 0, 0, w, h);
ctx.scale(scaleBy, scaleBy);
let previewImg = canvas.toDataURL("image/png");
// console.log('1');
this.down(previewImg);
},
async down(image) {
// 下载图片
let a = document.createElement("a"); // 创建一个a节点插入的document
var event = new MouseEvent("click"); // 模拟鼠标click点击事件
a.download = "downLoadImg"; // 设置a节点的download属性值
a.href = image; // 将图片的src赋值给a节点的href
a.dispatchEvent(event); // 触发鼠标点击事件
},
// 截屏结束
// 点击
dianj() {
if (this.dialogImageUrl.flg == 2) {
this.dialogImageUrl = this.dialogImageUrl1;
this.dialogImageUrl.flg = 1;
this.$refs.videoPlayer.player.pause();
} else {
this.dialogImageUrl = this.dialogImageUrl2;
this.dialogImageUrl.flg = 2;
this.$refs.videoPlayer.player.play();
}
},
// 音量
yingliang() {
this.yl = !this.yl;
},
// 关闭声音
volumeChange(val) {
// console.log(va);
this.$refs.videoPlayer.player.volume(val/100);
// if (this.songsss == 0) {
// this.songS = this.songS1;
// } else {
// this.songS = this.songS2;
// }
// console.log(this.songsss, "ddd");
},
swiperLeft() {
this.$refs.content.scrollLeft -= Math.max(this.$refs.content.clientWidth, 0)
},
swiperRight() {
this.$refs.content.scrollLeft += this.$refs.content.clientWidth
// let left = parseInt(this.$refs.currTime.style.left)
// let scrollLeft = this.$refs.content.scrollLeft
// console.log(left)
// this.$refs.currTime.style.left = left - scrollLeft + "px"
},
// 初始化多个视频位置
domOnLoad() {
let dateSecond = 24*60*60
for(var i = 0;i < this.videos.length;i++) {
let startTime = this._timeToSeconds(this.videos[i].startDateStr) // 开始
let endTime = this._timeToSeconds(this.videos[i].endDateStr) // 结束
let startPosi = startTime / dateSecond * this.$refs.item.clientWidth // 起始位置
let videoWidth = (endTime - startTime) / dateSecond * this.$refs.item.clientWidth //视频宽度
this.drops.push({
position: startPosi,
width: videoWidth,
duration: endTime - startTime,
status: this.videos[i].status
})
if(i===0) {
this.$refs.content.scrollLeft = startPosi
}
}
},
videoClick(e, index) {
let isSwitch = index !== this.currentVideo
this.currentVideo = index
let offsetX = e.offsetX
let second = offsetX / this.drops[this.currentVideo].width * this.drops[this.currentVideo].duration
this.videoSwitch(isSwitch, second)
let contentWidth = this.$refs.content.getBoundingClientRect().left
let scrollLeft = this.$refs.content.scrollLeft
let currTimeLeft = e.pageX - contentWidth + scrollLeft
// setTimeout(()=>{
// this.$refs.currTime.style.left = currTimeLeft+'px'
// }, 200)
},
// 视频切换
videoSwitch(switchVideo, time=0) {
if(switchVideo) {
this.playerOptions['sources'][0]['src'] = this.videos[this.currentVideo].historyUrl
console.log(this.videos[this.currentVideo].historyUrl)
}
setTimeout(()=>{
this.$refs.videoPlayer.player.currentTime(time)
this.$refs.videoPlayer.player.play()
}, 200)
},
// 视频播完回调
onPlayerEnded($event) {
this.dialogImageUrl = this.dialogImageUrl2;
this.currentVideo += 1
this.videoSwitch(true)
},
// 已开始播放回调
onPlayerPlaying($event) {
this.dialogImageUrl = this.dialogImageUrl2;
console.log("开始前的回调", $event);
},
// 暂停回调
onPlayerPause(player) {
this.dialogImageUrl = this.dialogImageUrl1;
console.log("暂停!", player);
},
// 当前播放位置发生变化时触发。
onPlayerTimeupdate(player) {
let currTime = player.cache_.currentTime
let allTime = player.cache_.duration
// let offsetX = currTime / allTime * this.drops[this.currentVideo].width // 指针偏移量
let offsetX = currTime / allTime * this.drops[this.currentVideo].width // 指针偏移量
let parentOffsetX = this.$refs.drops[this.currentVideo].offsetLeft // 盒子偏移量
let scrollLeft = this.$refs.content.scrollLeft
let allX = parentOffsetX + offsetX - scrollLeft
this.$refs.currTime.style.left = allX + 'px'
if(allX > this.$refs.content.clientWidth || (allX < 0 ? -allX > -this.$refs.content.clientWidth : false)) {
// this.swiperRight()
this.$refs.currTime.style.display = "none"
}else {
this.$refs.currTime.style.display = "block"
}
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied(player, playtimes) {
// console.log(playtimes);
player.currentTime(playtimes)///开始的位置 可以通过这个值来修改
},
fullScreenHandle() {
// console.log("全屏");
let rulerWrapper = this.$refs.rulerWrapper
let fullScreenBox = this.$refs.videoPlayer.player.el_
let player = this.$refs.videoPlayer.player
// fullScreenBox.appendChild(rulerWrapper)
if (!player.isFullscreen()) {
player.requestFullscreen();
player.isFullscreen(true);
} else {
player.exitFullscreen();
player.isFullscreen(false);
}
},
_formatSeconds(value) {
let second = value % 60
let minute = Math.floor(value/60) % 60
return ('00'+minute).slice(minute.toString().length)+':'+('00'+second).slice(second.toString().length)
},
_timeToSeconds(value) {
var s = '';
var hour = value.split(':')[0];
var min = value.split(':')[1];
var sec = value.split(':')[2];
s = Number(hour*3600) + Number(min*60) + Number(sec);
return s;
}
}
}
</script>
<style scoped>
.playback, .video-wrapper {
width: 100%;
height: 100%;
}
.hide {
opacity: 0;
}
.video-wrapper {
position: relative;
}
.video-wrapper:hover .hide {
opacity: 1;
}
.video-wrapper .bottom-controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
overflow-y: hidden;
overflow-x: hidden;
padding-top: 20px;
}
.video-wrapper .ruler-wrapper {
width: 100%;
background-color: rgba(0,0,0,0.6);
}
.ruler-wrap {
width: 100%;
height: 100px;
font-size: 0;
border: 1px solid #393b46;
border-width: 1px 0;
}
.video-wrapper .left, .video-wrapper .right {
display: inline-block;
vertical-align: top;
width: 5%;
height: 100px;
line-height: 100px;
cursor: pointer;
font-size: 27px;
font-weight: bold;
color: #ffffff;
text-align: center;
}
.ruler-content-wrap {
position: relative;
display: inline-block;
width: 90%;
}
.ruler-content-box {
position: relative;
height: 100px;
overflow: hidden;
}
.ruler-content {
position: relative;
display: inline-block;
width: 100%;
height: 120px;
overflow: hidden;
overflow-x: scroll;
}
.ruler-item {
font-size: 0;
height: 100px;
white-space: nowrap;
position: absolute;
z-index: 5;
}
.ruler-lists {
display: inline-block;
width: 35px;
height: 100px;
position: relative;
vertical-align: top;
}
.ruler-lists::before, .ruler-lists:first-child::after {
content: "";
display: inline-block;
width: 1px;
height: 18px;
background-color: #393b46;
opacity: 0.5;
position: absolute;
right: 0;
top: 0;
}
.ruler-lists:nth-child(6n)::before {
height: 50px;
}
.ruler-lists:first-child::before {
right: auto;
left: 0;
height: 50px;
}
.ruler-lists span {
line-height: 31px;
font-size: 24px;
color: #eeeeee;
vertical-align: top;
position: absolute;
left: 15px;
top: 30px;
z-index: 5;
}
.drop, .drop1 {
height: 100px;
position: absolute;
top: 0;
}
.drop {
z-index: 6;
}
.drop1 {
background-color: #2e71b2;
}
.drop1.red {
background-color: rgba(255,8,27,0.8);
}
.currTime {
display: inline-block;
width: 1px;
height: 100px;
position: absolute;
top: 0;
left: 0;
transition: left .3s;
z-index: 6;
}
.currTime::after {
content: "";
display: inline-block;
width: 40px;
height: 163px;
background: url(../../../assets/playback/currTime@2x.png);
background-size: cover;
transform: translateX(-50%);
}
/* -------- */
.control-wrapper {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0;
color: #fff;
background-color: rgba(0,0,0,0.6);
}
.pic {
padding: 5px;
width: 30px;
height: 30px;
cursor: pointer;
}
.bottomRight {
width: 15%;
display: flex;
justify-content: space-around;
}
.urlRight {
z-index: 100;
display: inline-block;
width: 23px;
height: 23px;
}
.urlRight .el-image {
width: 100%;
height: 100%;
cursor: pointer;
vertical-align: top;
}
.pr {
position: absolute !important;
top: -115px;
left: 50%;
transform: translateX(-50%);
}
</style>
<style lang="scss">
:root {
--ruler-height: 100px;
--ruler-pad-top: 29px;
--ruler-big-line: 50px;
--ruler-small-line: 18px;
--ruler-font-size: 24px;
--ruler-font-top: 29px;
--ruler-swiper-size: 30px;
--ruler-currTime-top: -49px;
--ruler-currTime-width: 40px;
--ruler-currTime-height: 163px;
--control-height: 90px;
--control-play-size: 50px;
--control-icon-size: 28px;
}
/* 刻度尺样式兼容 */
.video-wrapper {
.ruler-wrapper {
bottom: var(--control-height);
padding-top: var(--ruler-pad-top);
.ruler-wrap, .ruler-content-box {
height: var(--ruler-height);
}
.ruler-lists {
height: var(--ruler-height);
&::before {
height: var(--ruler-small-line);
}
&:nth-child(6n)::before, &:first-child::before {
height: var(--ruler-big-line);
}
span {
top: var(--ruler-font-top);
font-size: var(--ruler-font-size);
}
}
.left, .right {
height: var(--ruler-height);
line-height: var(--ruler-height);
font-size: var(--ruler-swiper-size);
}
.currTime {
top: var(--ruler-currTime-top);
&::after {
width: var(--ruler-currTime-width);
height: var(--ruler-currTime-height);
}
}
}
.control-wrapper {
height: var(--control-height);
line-height: var(--control-height);
.pic {
width: var(--control-play-size);
height: var(--control-play-size);
margin-left: 2.4%;
}
.urlRight {
position: relative;
width: var(--control-icon-size);
height: var(--control-icon-size);
}
}
}
</style>
加班万岁!