<template> <div class="video-player"> <video id="my-player" ref="videoPlayer" class="video-js vjs-big-play-centered" controlslist="nodownload" crossorigin="anonymous" oncontextmenu="return false;" controls ></video> </div> </template> <script> import videojs from 'video.js' import { throttle } from '@/utils/util' const mirrerIcon = '<svg t="1594374364946" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2814" width="16" height="16"><path d="M424.2 230.4c0.6 0.2 1.2 0.4 1.6 0.5L160.6 148c-2.9-0.6-6-0.9-9.2-0.9-24.5 0-44.4 19.9-44.4 44.4v634.8c0 24.6 19.9 44.5 44.4 44.5 3.2 0 6.2-0.4 9.2-0.9L432.4 785c17.9-9.2 30.1-27.9 30.1-49.3V283.2c-0.1-24.6-16.1-45.6-38.3-52.8z m-28.4 485.9l-222.1 59.2V242.3l222.1 59.2v414.8z m466.4-569.2c-3.2 0-6.2 0.4-9.2 0.9l-262 82c0.4-0.1 0.7-0.1 0.9-0.2-23.5 6.5-40.8 28-40.8 53.5v452.3c0 21.5 12.2 40.1 30.1 49.4l271.6 84.9c2.9 0.6 6.1 0.9 9.3 0.9 24.5 0 44.4-19.9 44.4-44.5V191.5c0.2-24.5-19.7-44.4-44.3-44.4z" p-id="2815" fill="#ffffff"></path></svg>' // 热键 const leftKey = 37 const rightKey = 39 const spaceKey = 32 const enterKey = 13 export default { props: { singHoldingTime: { type: Object, default: () => { return { // signing: { // beginTime: 3, // endTime: 5 // }, // holding: [ // { // beginTime: 20, // endTime: 25 // } // ] } } }, pointList: { type: Array, default: () => { return [ // { // text: '沙和尚', // time: 3 // }, // { // text: '沙和尚', // time: 5 // }, // { // text: '沙和尚', // time: 7 // } ] } }, url: { type: String, default: '' }, // 快进快退的s fastSeconds: { type: Number, default: 5 }, previewImage: { type: Array, default: () => { return [ // { // img: 'https://tupian.sioe.cn/b/bing-home-image/pic/20140801.jpg', // time: 3 // }, ] } } }, data() { return { player: null, duration: 0, signingColor: '#e11920', holdingColor: '#ffd800' } }, computed: { videoEle() { return this.$refs.videoPlayer } }, watch: { url: { handler(val) { // 重新设置video的视频来源 if (this.player) { this.player.pause() this.player.src(val) this.player.load() } } } }, mounted() { this.init() }, beforeDestroy() { if (this.player) { this.player.dispose() } }, // TODO 确认是否删除 // beforeRouteLeave(to, from, next) { // this.stopVideo() // next() // }, methods: { init() { this.player = videojs( 'my-player', { playbackRates: [1, 1.25, 1.5, 2, 2.5], userActions: { // doubleClick: this.myDoubleClickHandler, hotkeys: this.hotkeys }, controlBar: { fullscreenToggle: false, volumePanel: { inline: false } }, notSupportedMessage: '视频加载失败' }, () => { this.player.on('loadedmetadata', () => { this.duration = this.player.duration() // 获取视频长度 this.createProgress() }) this.player.on('timeupdate', throttle(this.timeupdate, 1000, true)) // this.addMirrorComponents() } ) // this.player.on('pause', () => { // // Modals are temporary by default. They dispose themselves when they are // // closed; so, we can create a new one each time the player is paused and // // not worry about leaving extra nodes hanging around. // var modal = this.player.createModal('This is a modal!') // // When the modal closes, resume playback. // modal.on('modalclose', () => { // this.player.play() // }) // }) this.player.src(this.url) }, timeupdate() { // 获取视频当前播放时间 this.$emit('timeupdate', this.videoEle.currentTime) }, myDoubleClickHandler() { // console.log('doubleclick') }, hotkeys(e) { if (e.which === spaceKey || e.which === enterKey) { this.playVideo() } if (e.which === leftKey) { this.fastForward(-this.fastSeconds) } if (e.which === rightKey) { this.fastForward(this.fastSeconds) } }, /** * 暂停或播放视频 */ playVideo() { if (this.videoEle) { if (this.videoEle.paused) { this.videoEle.play() } else { this.videoEle.pause() } } }, /** * 强制暂停 */ stopVideo() { this.videoEle.pause() }, /** * 快进视频 */ fastForward(time) { this.videoEle.play() if (this.videoEle.currentTime !== 0) this.videoEle.currentTime += time }, // 设置视频时间 setVideoCurrentTime(time) { this.videoEle.currentTime = time - 0 }, // 添加Mirror镜像控件 addMirrorComponents() { const domTemp = `<button class="vjs-picture-in-picture-control vjs-control vjs-button" type="button" title="Picture-in-Picture" aria-disabled="false"> ${mirrerIcon} <span class="vjs-control-text" aria-live="polite">Picture-in-Picture</span> </button>` const control = document.getElementsByClassName('vjs-control-bar')[0] const span = document.createElement('span') span.innerHTML = domTemp control.appendChild(span) let bool = true span.addEventListener('click', () => { const video = document.getElementsByTagName('video')[0] bool = !bool bool ? (video.style.transform = 'rotateY(0)') : (video.style.transform = 'rotateY(180deg)') }) }, createProgress() { // 视频进度条做标记 const progress = document.getElementsByClassName('vjs-progress-holder')[0] const delDotDiv = document.getElementsByClassName( 'video-face-pair-list' )[0] delDotDiv && progress.removeChild(delDotDiv) const dotDiv = document.createElement('div') dotDiv.setAttribute('class', 'video-face-pair-list') // 视频人脸对比标记 Object.keys(this.singHoldingTime).forEach(a => { const item = this.singHoldingTime[a] const colorDiv = document.createElement('div') colorDiv.setAttribute('class', 'face-pair') if (Array.isArray(item)) { item.forEach(sonItem => { if (sonItem.beginTime < 0) return colorDiv.style.width = ((sonItem.endTime - sonItem.beginTime) / this.duration) * 100 + '%' colorDiv.style.left = (sonItem.beginTime / this.duration) * 100 + '%' colorDiv.style.background = this.holdingColor }) } else { if (item.beginTime < 0) return colorDiv.style.width = ((item.endTime - item.beginTime) / this.duration) * 100 + '%' colorDiv.style.left = (item.beginTime / this.duration) * 100 + '%' colorDiv.style.background = this.signingColor } dotDiv.appendChild(colorDiv) }) // 视频预览图 this.previewImage.forEach(item => { const spanDom = document.createElement('span') spanDom.classList.add('preview-image-dot') spanDom.style.left = `${(item.time / this.duration) * 100}%` spanDom.innerHTML = `<div class="preview-wrapper"> <img src="${item.img}" alt="" /> </div>` dotDiv.appendChild(spanDom) }) // 视频关键打点 this.pointList.forEach(item => { const spanDom = document.createElement('span') spanDom.classList.add('video-dot') spanDom.style.left = `${(item.time / this.duration) * 100}%` spanDom.innerHTML = `<div class="video-dot-tip"> ${item.text} </div>` dotDiv.appendChild(spanDom) }) progress.appendChild(dotDiv) } } } </script> <style lang="scss" scope> .video-player { width: 100%; height: 100%; position: relative; .video-js { height: 100%; width: 100%; } // .color-span { // background: black; // width: 130px; // height: 70px; // padding: 5px; // position: absolute; // top: 0; // left: 10px; // color: white; // font-size: 13px; // display: flex; // flex-direction: column; // justify-content: space-around; // align-items: flex-start; // .holding, // .signing { // display: flex; // justify-content: center; // align-items: center; // span:first-child { // display: inline-block; // width: 40px; // height: 20px; // background: red; // margin-right: 10px; // } // span:last-child { // line-height: 20px; // } // } // .holding { // span:first-child { // background: yellow; // } // } // } #my-player.video-js { .video-face-pair-list { position: absolute; top: 0; left: 0; width: 100%; height: 100%; .face-pair { position: absolute; height: inherit; } .video-dot { position: absolute; top: -2px; width: 8px; height: 8px; border-radius: 50%; background-color: #fff; overflow: visible; z-index: 1000; box-shadow: 0 0 2px 2px #999; } .video-dot:hover { cursor: pointer; } .video-dot-tip { position: absolute; display: none; top: -76px; left: -75px; max-width: 250px; min-width: 150px; max-height: 60px; min-height: 25px; line-height: 25px; padding: 5px; background-color: #fff; box-shadow: 0 0 -2px 3px #adadad; overflow: auto; letter-spacing: 1px; color: #3d3d3d; border-radius: 5px; z-index: 10; font-size: 12px; font-family: 'Microsoft YaHei'; } .video-dot:hover { .video-dot-tip { display: block; } } .preview-image-dot { display: inline-block; position: absolute; left: 0; top: -11px; width: 0; height: 0; border-right: 5px solid #1a1f26; border-left: 5px solid #1a1f26; // border-top: 4px solid #3248a8; border-top: 5px solid #fff; .preview-wrapper { display: none; position: absolute; bottom: 18px; left: -50px; width: 130px; height: 80px; object-fit: contain; img { width: 100%; height: 100%; } } } .preview-image-dot:hover { background-color: lightblue; .preview-wrapper { display: block; } } } } } </style> <style lang="scss"> .vjs-paused .vjs-big-play-button, .vjs-paused.vjs-has-started .vjs-big-play-button { display: block; } </style>