视频回放
This commit is contained in:
		| @@ -16,10 +16,8 @@ | ||||
|           class="videoBox" | ||||
|           v-for="(m, i) in monitors" | ||||
|           :key="m.id" | ||||
|           @mouseenter.stop="m.isShowPlayBtn = true" | ||||
|           @mouseleave.stop="m.isShowPlayBtn = false" | ||||
|           :style="currentSplitStyle"> | ||||
|           <AiMonitor :src="m.url" type="slw" :name="m.name" @close="removeMonitor(i)"></AiMonitor> | ||||
|           <AiMonitor :src="m.url" type="slw" :name="m.name" @close="removeMonitor(i)" ref="AiMonitor"></AiMonitor> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -116,6 +114,12 @@ export default { | ||||
|       if (e === 1 && this.monitors.length) { | ||||
|         this.monitors = [this.monitors[0]] | ||||
|       } | ||||
|  | ||||
|       console.log(this.$refs.AiMonitor) | ||||
|  | ||||
|       this.$refs.AiMonitor.forEach(e => { | ||||
|         e.reset() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     removeMonitor(i) { | ||||
| @@ -217,7 +221,7 @@ export default { | ||||
|   height: 100%; | ||||
|  | ||||
|   .monitorPane { | ||||
|     color: #fff; | ||||
|     // color: #fff; | ||||
|     flex: 1; | ||||
|     min-width: 0; | ||||
|     padding: 20px 20px 20px 4px; | ||||
|   | ||||
| @@ -14,33 +14,53 @@ | ||||
|       style="width: 100%; height: 100%;" | ||||
|       :src="`https://cdn.cunwuyun.cn/slw2.0/index.html?url=${src}`"> | ||||
|     </iframe> | ||||
|     <div class="action-bar"> | ||||
|       <div class="left"> | ||||
|         <div class="left-btns"> | ||||
|           <span :class="[currIndex === 0 ? 'active' : '']" @click="currIndex = 0">实时</span> | ||||
|           <span :class="[currIndex === 1 ? 'active' : '']" @click="currIndex = 1">历史</span> | ||||
|         </div> | ||||
|         <div | ||||
|           class="volume" | ||||
|           @mouseleave.stop="isShowVolume = false"> | ||||
|           <img  | ||||
|             @mouseenter.stop="isShowVolume = true" | ||||
|             src="https://cdn.cunwuyun.cn/slw2.0/images/sound.png"> | ||||
|           <div class="volume-slider" :class="[isShowVolume ? 'active' : '']"> | ||||
|             <el-slider | ||||
|               input-size="mini" | ||||
|               v-model="volume" | ||||
|               vertical | ||||
|               @change="onVolume" | ||||
|               height="80px"> | ||||
|             </el-slider> | ||||
|     <div class="slw-bottom"> | ||||
|       <Timeline class="Timeline" @pause="isLiveing = false" :isLiveing="isLiveing" :width="width" ref="timeline" :style="{width: width}"></Timeline> | ||||
|       <div class="action-bar"> | ||||
|         <div class="left"> | ||||
|           <div class="left-btns"> | ||||
|             <el-tooltip effect="dark" :content="isPause ? '播放' : '暂停'" placement="top"> | ||||
|               <img | ||||
|                 :src="isPause ? 'https://cdn.cunwuyun.cn/slw2.0/images/play.png' : 'https://cdn.cunwuyun.cn/slw2.0/images/pause.png'" | ||||
|                 @click="changePlayStatus"> | ||||
|             </el-tooltip> | ||||
|           </div> | ||||
|           <div | ||||
|             class="volume" | ||||
|             @mouseleave.stop="isShowVolume = false"> | ||||
|             <img  | ||||
|               @mouseenter.stop="isShowVolume = true" | ||||
|               src="https://cdn.cunwuyun.cn/slw2.0/images/sound.png"> | ||||
|             <div class="volume-slider" :class="[isShowVolume ? 'active' : '']"> | ||||
|               <el-slider | ||||
|                 input-size="mini" | ||||
|                 v-model="volume" | ||||
|                 vertical | ||||
|                 @change="onVolume" | ||||
|                 height="80px"> | ||||
|               </el-slider> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="play-status"> | ||||
|             <div class="live" v-if="isLiveing"> | ||||
|               <span class="label"></span> | ||||
|               <i>直播中</i> | ||||
|               <em>{{ form.date }}</em> | ||||
|             </div> | ||||
|             <div v-else class="back-btn" @click="isLiveing = true">回到直播</div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="right"> | ||||
|         <img src="https://cdn.cunwuyun.cn/slw2.0/images/date.png" @click="isShowDate = true"> | ||||
|         <img src="https://cdn.cunwuyun.cn/slw2.0/images/screenshots.png" title="截屏" @click="screenshots"> | ||||
|         <img src="https://cdn.cunwuyun.cn/slw2.0/images/full-screen.png" :title="isFullscreen ? '退出全屏' : '全屏'" @click="fullscreen"> | ||||
|         <div class="right"> | ||||
|           <el-tooltip effect="dark" content="选择日期" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/date.png" @click="isShowDate = true"> | ||||
|           </el-tooltip> | ||||
|           <el-tooltip effect="dark" content="截屏" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/screenshots.png" @click="screenshots"> | ||||
|           </el-tooltip> | ||||
|           <el-tooltip effect="dark" :content="isFullscreen ? '退出全屏' : '全屏'" placement="top"> | ||||
|             <img src="https://cdn.cunwuyun.cn/slw2.0/images/full-screen.png" @click="fullscreen"> | ||||
|           </el-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <ai-dialog | ||||
| @@ -63,11 +83,17 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import Timeline from './Timeline' | ||||
|  | ||||
|   export default { | ||||
|     props: ['src', 'name'], | ||||
|  | ||||
|     name: 'slwVideo', | ||||
|  | ||||
|     components: { | ||||
|       Timeline | ||||
|     }, | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         currIndex: 0, | ||||
| @@ -75,9 +101,13 @@ | ||||
|         isShowPlayBtn: false, | ||||
|         isShow: true, | ||||
|         isShowVolume: false, | ||||
|         isLiveing: true, | ||||
|         form: { | ||||
|           date: '' | ||||
|         }, | ||||
|         date: '', | ||||
|         isPause: false, | ||||
|         width: '', | ||||
|         volume: 100, | ||||
|         videoId: `slwvideo-${new Date().getTime()}`, | ||||
|         id: `video-${new Date().getTime()}`, | ||||
| @@ -102,6 +132,10 @@ | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.form.date = this.$moment(new Date()).format('YYYY-MM-DD') | ||||
|       this.$nextTick(() => { | ||||
|         this.width = document.querySelector(`#${this.videoId}`).offsetWidth + 'px' | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
| @@ -117,6 +151,16 @@ | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       changePlayStatus () { | ||||
|         const subPage = document.querySelector(`#${this.id}`).contentWindow | ||||
|         subPage.postMessage({ | ||||
|           type: 'play', | ||||
|           value: this.isPause | ||||
|         }, '*') | ||||
|          | ||||
|         this.isPause = !this.isPause | ||||
|       }, | ||||
|  | ||||
|       onConfirm () { | ||||
|         this.$refs.form.validate((valid) => { | ||||
|           if (valid) { | ||||
| @@ -141,6 +185,16 @@ | ||||
|         } | ||||
|  | ||||
|         this.isFullscreen = !this.isFullscreen | ||||
|         this.reset() | ||||
|       }, | ||||
|  | ||||
|       reset () { | ||||
|         setTimeout(() => { | ||||
|           this.width = document.querySelector(`#${this.videoId}`).offsetWidth + 'px' | ||||
|           this.$nextTick(() => { | ||||
|             this.$refs.timeline.init() | ||||
|           }) | ||||
|         }, 60) | ||||
|       }, | ||||
|  | ||||
|       screenshots () { | ||||
| @@ -174,11 +228,31 @@ | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     iframe { | ||||
|       border: none; | ||||
|     } | ||||
|  | ||||
|     .slw-bottom { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       width: 100%; | ||||
|       transition: all ease-in-out 0.5s; | ||||
|       // transform: translateY(100%); | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       .slw-title { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|       .slw-bottom { | ||||
|         transform: translateY(0%); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .slw-title { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
| @@ -192,11 +266,17 @@ | ||||
|       line-height: 40px; | ||||
|       padding: 0 16px; | ||||
|       background: rgba(0, 0, 0, 0.8); | ||||
|       transition: all ease 0.5s; | ||||
|       transform: translateY(-100%); | ||||
|  | ||||
|       h2 { | ||||
|         max-width: 70%; | ||||
|         font-size: 16px; | ||||
|         color: #fff; | ||||
|         font-weight: normal; | ||||
|         white-space: nowrap; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis;   | ||||
|       } | ||||
|  | ||||
|       .slw-title__close { | ||||
| @@ -228,19 +308,80 @@ | ||||
|  | ||||
|     .action-bar { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
|       align-items: center; | ||||
|       justify-content: space-between; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       width: 100%; | ||||
|       height: 40px; | ||||
|       padding: 0 16px; | ||||
|       background: rgba(0, 0, 0, 0.8); | ||||
|  | ||||
|       .left { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|  | ||||
|         .play-status { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           margin-left: 12px; | ||||
|  | ||||
|           em { | ||||
|             margin-left: 12px; | ||||
|             font-style: normal; | ||||
|             color: #fff; | ||||
|             font-size: 12px; | ||||
|           } | ||||
|  | ||||
|           .back-btn { | ||||
|             padding: 4px 10px; | ||||
|             border-radius: 6px; | ||||
|             color: #ddd; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|             background: #343747; | ||||
|  | ||||
|             &:hover { | ||||
|               opacity: 0.6; | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           .live { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             padding: 2px 5px; | ||||
|             color: rgba(0,255,0,.8); | ||||
|  | ||||
|             i { | ||||
|               font-size: 12px; | ||||
|               font-style: normal; | ||||
|             } | ||||
|  | ||||
|             .label { | ||||
|               width: 6px; | ||||
|               height: 6px; | ||||
|               margin-right: 12px; | ||||
|               background: rgba(0,255,0,.8); | ||||
|               border-radius: 50%; | ||||
|               position: relative; | ||||
|  | ||||
|               &:after { | ||||
|                 content: ""; | ||||
|                 width: 12px; | ||||
|                 height: 12px; | ||||
|                 position: absolute; | ||||
|                 left: 50%; | ||||
|                 top: 50%; | ||||
|                 -webkit-transform: translate(-50%,-50%); | ||||
|                 transform: translate(-50%,-50%); | ||||
|                 border-radius: 50%; | ||||
|                 border: 4px solid rgba(0,255,0,.2); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .volume { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           position: relative; | ||||
|           height: 100%; | ||||
|  | ||||
| @@ -271,13 +412,7 @@ | ||||
|         .left-btns { | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           width: 96px; | ||||
|           height: 24px; | ||||
|           margin-right: 10px; | ||||
|           background: #222838; | ||||
|           border-radius: 100px; | ||||
|           text-align: center; | ||||
|           overflow: hidden; | ||||
|  | ||||
|           span { | ||||
|             flex: 1; | ||||
|   | ||||
							
								
								
									
										262
									
								
								packages/IntelligentSecurity/components/Timeline.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								packages/IntelligentSecurity/components/Timeline.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | ||||
| <template> | ||||
|   <div class="canvas" @mousemove.stop="onMousemove" @mouseup="onMouseUp" @mouseleave="isHide = true" v-if="isInit"> | ||||
|     <canvas | ||||
|       :id="id" | ||||
|       :style="{height: '52px'}" | ||||
|       v-if="canvasWidth" | ||||
|       :width="canvasWidth" | ||||
|       height="52"> | ||||
|     </canvas> | ||||
|     <div class="time" v-show="!isHide" :style="{left: left + 'px'}">{{ time }}</div> | ||||
|     <div class="time-scale" :style="{left: x + 'px'}"> | ||||
|       <span></span> | ||||
|     </div> | ||||
|     <img | ||||
|       @mousedown="onDragDown" | ||||
|       class="drag-img" | ||||
|       :style="{left: x + 'px'}" | ||||
|       src="https://cdn.cunwuyun.cn/slw2.0/images/drag.png" /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     props: ['isLiveing'], | ||||
|  | ||||
|     data () { | ||||
|       return { | ||||
|         ctx: null, | ||||
|         canvasWidth: '', | ||||
|         canvasHeight: '', | ||||
|         scale: 4, | ||||
|         time: '', | ||||
|         left: 0, | ||||
|         x: 0, | ||||
|         isHide: true, | ||||
|         isInit: false, | ||||
|         isChoose: false, | ||||
|         id: `timeline-${new Date().getTime()}`, | ||||
|         timer: null | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     watch: { | ||||
|       isLiveing (v) { | ||||
|         if (v) { | ||||
|           this.countdown() | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     mounted () { | ||||
|       this.$nextTick(() => { | ||||
|         this.init() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|       onMousemove (e) { | ||||
|         const canvasInfo = document.querySelector(`#${this.id}`).getBoundingClientRect() | ||||
|         const seconds = 24 * 60 * 60 | ||||
|  | ||||
|         if (e.clientY - canvasInfo.top < 29) { | ||||
|           const x = e.clientX - canvasInfo.left | ||||
|           const unit = seconds / this.canvasWidth * x | ||||
|           this.left = x | ||||
|           this.time = this.secTotime(unit) | ||||
|           this.isHide = false | ||||
|  | ||||
|           if (!this.isChoose) return | ||||
|           this.x = e.clientX - canvasInfo.left - 4 | ||||
|           this.ratioW = this.x / this.canvasWidth | ||||
|         } else { | ||||
|           this.isHide = true | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       onDragDown () { | ||||
|         this.$emit('pause') | ||||
|         clearInterval(this.timer) | ||||
|         this.timer = null | ||||
|         this.isChoose = true | ||||
|       }, | ||||
|  | ||||
|       onMouseUp () { | ||||
|         this.isChoose = false | ||||
|       }, | ||||
|  | ||||
|       secTotime (s) { | ||||
|         var t = '' | ||||
|         if(s > -1){ | ||||
|           var hour = Math.floor(s / 3600) | ||||
|           var min = Math.floor(s / 60) % 60 | ||||
|           var sec = s % 60 | ||||
|           if(hour < 10) { | ||||
|             t = '0'+ hour + ":" | ||||
|           } else {  | ||||
|             t = hour + ":" | ||||
|           }  | ||||
|           if(min < 10){ | ||||
|             t += "0" | ||||
|           }  | ||||
|           t += min + ":" | ||||
|           if(sec < 10){ | ||||
|             t += "0" | ||||
|           }  | ||||
|           t += sec.toFixed(0) | ||||
|         }  | ||||
|         return t | ||||
|       }, | ||||
|  | ||||
|       init () { | ||||
|         this.$nextTick(() => { | ||||
|           this.isInit = true | ||||
|           this.$nextTick(() => { | ||||
|             this.canvasWidth = document.querySelector('.canvas').offsetWidth | ||||
|             this.canvasHeight = document.querySelector('.canvas').offsetHeight | ||||
|  | ||||
|             this.$nextTick(() => { | ||||
|               const el = document.querySelector(`#${this.id}`) | ||||
|               this.ctx = el.getContext('2d') | ||||
|               this.ctx.width  = document.querySelector('.canvas').offsetWidth | ||||
|               this.ctx.height  = document.querySelector('.canvas').offsetHeight | ||||
|  | ||||
|               if (this.x > 0) { | ||||
|                 this.x = this.canvasWidth * this.ratioW | ||||
|               } else { | ||||
|                 this.initNowTime() | ||||
|               } | ||||
|  | ||||
|               this.renderTimeLine() | ||||
|               this.renderPlayback() | ||||
|  | ||||
|               this.countdown() | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }, | ||||
|  | ||||
|       countdown () { | ||||
|         this.timer = setInterval(() => { | ||||
|           this.initNowTime() | ||||
|         }, 1000) | ||||
|       }, | ||||
|  | ||||
|       initNowTime () { | ||||
|         const date = new Date() | ||||
|         const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() | ||||
|  | ||||
|         this.x = seconds / (24 * 60 * 60) * this.canvasWidth | ||||
|       }, | ||||
|  | ||||
|       drawLine(ctx, options) { | ||||
|         const { beginX, beginY, endX, endY, lineColor, lineWidth } = options | ||||
|         ctx.beginPath() | ||||
|         ctx.lineWidth = lineWidth | ||||
|         ctx.moveTo(beginX, beginY) | ||||
|         ctx.lineTo(endX, endY) | ||||
|         ctx.strokeStyle = lineColor | ||||
|         ctx.stroke() | ||||
|       }, | ||||
|  | ||||
|       renderPlayback () { | ||||
|         const ctx = this.ctx | ||||
|         for (let i = 0; i < 24 * 60 * 60; i ++) { | ||||
|           if ((i + 1) % 3 !== 0) { | ||||
|             this.drawLine(ctx, { | ||||
|               beginX: i, | ||||
|               beginY: 28, | ||||
|               endX: i, | ||||
|               endY: 0, | ||||
|               lineColor: 'rgba(0, 156, 255, 1)', | ||||
|               lineWidth: 1 | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       renderTimeLine () { | ||||
|         const ctx = this.ctx | ||||
|         ctx.fillStyle = 'rgba(51, 60, 83, 0.8)' | ||||
|         ctx.fillRect(0, 0, this.canvasWidth, 28) | ||||
|         ctx.fillStyle = 'rgba(32, 40, 61, 0.8)' | ||||
|         ctx.fillRect(0, 28, this.canvasWidth, 24) | ||||
|  | ||||
|          | ||||
|         ctx.fillStyle = '#fff' | ||||
|         ctx.font = '12px Arial' | ||||
|         const w = this.canvasWidth / 24  | ||||
|  | ||||
|         for (let i = 1; i < 25; i ++) { | ||||
|           this.drawLine(ctx, { | ||||
|             beginX: i * w, | ||||
|             beginY: 28, | ||||
|             endX: i * w, | ||||
|             endY: i % this.scale === 0 ? 16 : 20, | ||||
|             lineColor: i % this.scale === 0 ? 'red' : '#000', | ||||
|             lineWidth: i % this.scale === 0 ? 1 : 1 | ||||
|           }) | ||||
|  | ||||
|           if (i % this.scale === 0) { | ||||
|             const text = (i < 10 ? '0' + i : i) + ': 00' | ||||
|             const textWidth = ctx.measureText(text).width | ||||
|             if (i === 24) { | ||||
|               ctx.fillText(text, i * w - textWidth, 44) | ||||
|             } else { | ||||
|               ctx.fillText(text, i * w - textWidth / 2, 44) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   .canvas { | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
|     height: 52px; | ||||
|  | ||||
|     .drag-img { | ||||
|       position: absolute; | ||||
|       left: 0; | ||||
|       top: 0; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       cursor: e-resize; | ||||
|       -webkit-user-drag: none; | ||||
|     } | ||||
|  | ||||
|     .time-scale { | ||||
|       display: flex; | ||||
|       position: absolute; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       left: 0; | ||||
|       bottom: 0; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|       width: 12px; | ||||
|       height: 24px; | ||||
|  | ||||
|       span { | ||||
|         width: 2px; | ||||
|         height: 24px; | ||||
|         background: rgba(255, 255, 255, 0.8); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .time { | ||||
|       position: absolute; | ||||
|       bottom: 22px; | ||||
|       left: 0; | ||||
|       z-index: 1; | ||||
|       padding: 2px 4px; | ||||
|       font-size: 12px; | ||||
|       color: #fff; | ||||
|       background: rgba(0, 0, 0, 1); | ||||
|       transform: translate(-50%, 100%); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user