123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- <template>
- <div>
- <el-dialog v-el-drag-dialog :visible.sync="showMobile" title="" :close-on-click-modal="false" :close-on-press-escape="false" :custom-class="mobileStyle" @close="close">
- <div slot="title" class="cfff fs14">
- <span class="ml20">{{ mobileName }}</span>
- <el-dropdown placement="bottom" class="fr mr100 oln" trigger="hover" @command="handleCommand" @visible-change="handleVisble">
- <span class="fs14 cfff">{{ bitRateText }}<i :class="caretIcon" /></span>
- <el-dropdown-menu slot="dropdown" class="mobile-dropdown-menu">
- <el-dropdown-item :class="bitRateStyle('1638400')" command="1638400">自动</el-dropdown-item>
- <el-dropdown-item :class="bitRateStyle('491520')" command="491520">极速</el-dropdown-item>
- <el-dropdown-item :class="bitRateStyle('1392640')" command="1392640">标清</el-dropdown-item>
- <el-dropdown-item :class="bitRateStyle('2785280')" command="2785280">高清</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </div>
- <div class="frns">
- <div class="mobile-audio">
- <video
- ref="player"
- width="372px"
- :controls="false"
- autoplay
- @play="ready"
- @error="error"
- @pause="pause"
- @mousedown="handleMouseDown"
- @mousemove="handleMouseMove"
- @mouseup="handleMouseUp"
- @mouseleave="handleMouseLeave"
- />
- <audio
- ref="audioPlayer"
- width="372px"
- controls
- autoplay
- poster="@/assets/equipment/loader-thumb.jpg"
- />
- <div v-if="isMask" class="mask">
- <i v-if="isLoading" class="el-icon-loading" />
- <img v-if="isError" class="w180h148" src="@/assets/equipment/guzhang_lixian_pic.png" alt="">
- <div class="c999 fs12 mt15">{{ maskText }}</div>
- </div>
- </div>
- <div class="mobile-sidebar">
- <i :class="volumeUpIcon" @click="handleRoutine(24)" />
- <i :class="volumeDownIcon" @click="handleRoutine(25)" />
- <el-popover placement="bottom-start" popper-class="nav-popover" width="109" trigger="hover">
- <div class="tac ptb10 c333 fs14">重启</div>
- <i slot="reference" :class="shutDownIcon" @click="reboot" />
- </el-popover>
- <el-popover placement="bottom-start" popper-class="nav-popover" width="109" trigger="hover">
- <div class="tac ptb10 c333 fs14">恢复出厂设置</div>
- <i slot="reference" :class="restartIcon" @click="recovery" />
- </el-popover>
- </div>
- </div>
- <div class="mobile-bottombar">
- <i :class="menuIcon" @click="handleRoutine(187)" />
- <i :class="homeIcon" @click="handleRoutine(3)" />
- <i :class="backIcon" @click="handleRoutine(4)" />
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import elDragDialog from '@/directive/el-drag-dialog'
- import JMuxer from 'jmuxer'
- import { getcardInfoBySn, rebootBatch, recovery, getcardStatus } from '@/api/mobile'
- import { ExexuteMouseDown, ExexuteMouseMove, ExexuteMouseUp, ExexuteKeyDown, ExexutePixel, ExexuteCloseServer, CheckScreenDirection, RequestIFrame, ConfigChannel } from '@/utils/control'
- import { keycodeMode } from '@/utils/config'
- const VIDEO_WIDTH = 371
- const VIDEO_HEIGHT = 660
- export default {
- name: 'DragDialogDemo',
- directives: { elDragDialog },
- props: {
- sn: {
- type: Array,
- default: () => {
- return []
- }
- },
- mobileName: {
- type: String,
- default: ''
- },
- mobileId: {
- type: Number,
- default: 0
- }
- },
- data() {
- return {
- showMobile: true,
- fpsCount: 0,
- timeCount: 0,
- isFeed: true,
- curTime: new Date().getTime(),
- requestTime: new Date().getTime(),
- jmuxer: null,
- audioMuxer: null,
- ip: '', // 设备ip
- port: '', // 设备端口号
- size: '', // 设备分辨率
- bitRate: '', // 设备清晰度
- path: 'ws://192.168.11.66:9101', // websocket地址
- socket: null, // websocket类
- isDrag: false,
- isLoading: true, // 加载中
- isMask: true, // 遮罩层
- maskText: '', // 加载文字
- isError: false, // 是否设备故障或者离线
- visble: false, // 下拉菜单是否展开
- isRotate: false, // 是否横屏
- isFlag: false
- }
- },
- computed: {
- bitRateStyle() {
- return (num) => {
- return this.bitRate === num ? 'pl20 bgcf3f6ff c515ff0 fs14' : 'pl20 c333 fs14'
- }
- },
- bitRateText() {
- /**
- * 极速 | 491520,60KB/S
- * 标清 | 1392640 ,170KB/S
- * 自动 | 1638400 ,200KB/S
- * 高清 | 2785280 ,340KB/S
- */
- const option = {
- '1638400': '自动',
- '491520': '极速',
- '1392640': '标清',
- '2785280': '高清'
- }
- return option[this.bitRate]
- },
- volumeUpIcon() {
- return this.isLoading || this.isError ? 'icon-volume-up disabled mt50' : 'icon-volume-up mt50'
- },
- volumeDownIcon() {
- return this.isLoading || this.isError ? 'icon-volume-down disabled mt40' : 'icon-volume-down mt40'
- },
- shutDownIcon() {
- return this.isLoading || this.isError ? 'icon-shutdown disabled mt226ormt90' : 'icon-shutdown mt226ormt90'
- },
- restartIcon() {
- return this.isLoading || this.isError ? 'icon-restart disabled mt40' : 'icon-restart mt40'
- },
- menuIcon() {
- return this.isLoading || this.isError ? 'icon-background disabled' : 'icon-background'
- },
- homeIcon() {
- return this.isLoading || this.isError ? 'icon-index disabled mlr64ormlr160' : 'icon-index mlr64ormlr160'
- },
- backIcon() {
- return this.isLoading || this.isError ? 'icon-back disabled' : 'icon-back'
- },
- caretIcon() {
- return this.visble ? 'el-icon-caret-top ml5' : 'el-icon-caret-bottom ml5'
- },
- mobileStyle() {
- return this.isRotate ? 'mobile-dialog rotate' : 'mobile-dialog'
- }
- },
- created() {
- this.getcardInfoBySn()
- },
- mounted() {
- document.addEventListener('visibilitychange', this.audioPlay)
- document.addEventListener('keydown', this.handleKeyDown)
- this.$nextTick(() => {
- this.jmuxer = new JMuxer({
- node: this.$refs.player,
- flushingTime: 15,
- fps: 30,
- mode: 'video',
- debug: false
- })
- this.audioMuxer = new JMuxer({
- node: this.$refs.audioPlayer,
- flushingTime: 1,
- clearBuffer: true,
- fps: 43, // 可以不选
- mode: 'audio',
- debug: false
- })
- })
- this.curTime = new Date().getTime()
- },
- destroyed() {
- document.removeEventListener('visibilitychange', this.audioPlay)
- document.removeEventListener('keydown', this.handleKeyDown)
- this.socket.send(ExexuteCloseServer(this.sn.join(','))) // 销毁监听
- this.socket.close()
- },
- methods: {
- handleVisble(flag) {
- this.visble = flag
- },
- // 设备恢复出厂设置
- recovery() {
- if (this.isLoading || this.isError) {
- this._message.warning('操作过于频繁,请稍后再试')
- return
- }
- this.$confirm('<div class="c666 fs18 mb10">确认要恢复出厂设置吗?</div><div class="c999 fs14">恢复出厂后将恢复到初始设置并清除所有数据,恢复出厂过程/n中设备将不可操作。</div>', '恢复出厂', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- customClass: 'cloud-phone-message-box', // 自定义消息样式类名
- confirmButtonClass: 'cloud-phone-confirm-btn', // 自定义确认按钮样式类名
- cancelButtonClass: 'cloud-phone-cancel-btn' // 自定义取消按钮样式类名
- }).then(() => {
- recovery({ sns: this.sn.join(',') }).then(res => {
- if (res.status === 0) {
- this.$alert('<div class="c666 fs18 mb10">恢复出厂设置指令已发送</div><div class="c999 fs14">若恢复出厂设置失败可重试</div>', '恢复出厂', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确认',
- customClass: 'cloud-phone-message-box',
- confirmButtonClass: 'cloud-phone-confirm-btn', // 自定义确认按钮样式类名
- callback: action => {
- this.isMask = true
- this.isLoading = true
- this.maskText = '设备恢复出厂设置中...'
- this._send()
- }
- })
- } else {
- this._message.error('恢复出厂设置指令发送失败,请稍后再试')
- }
- })
- }).catch(() => {})
- },
- // 设备重启
- reboot() {
- if (this.isLoading || this.isError) {
- this._message.warning('操作过于频繁,请稍后再试')
- return
- }
- this.$confirm('<div class="c666 fs18 mb10">确认要重启设备吗?</div><div class="c999 fs14">重启后将完全关闭后台进程,重启过程中设备将不可操作。</div>', '重启', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- customClass: 'cloud-phone-message-box', // 自定义消息样式类名
- confirmButtonClass: 'cloud-phone-confirm-btn', // 自定义确认按钮样式类名
- cancelButtonClass: 'cloud-phone-cancel-btn' // 自定义取消按钮样式类名
- }).then(() => {
- rebootBatch({ sns: this.sn.join(',') }).then(res => {
- if (res.status === 0) {
- this.$alert('<div class="c666 fs18 mb10">重启指令已发送</div><div class="c999 fs14">若重启失败可重试</div>', '重启', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确认',
- customClass: 'cloud-phone-message-box',
- confirmButtonClass: 'cloud-phone-confirm-btn', // 自定义确认按钮样式类名
- callback: action => {
- this.isMask = true
- this.isLoading = true
- this.maskText = '设备重启中...'
- this._send()
- }
- })
- } else {
- this._message.error('重启指令发送失败,请稍后再试')
- }
- })
- }).catch(() => {})
- },
- async _send() {
- const state = await this.getcardStatus()
- if (state === '1') {
- this.socket.send(ExexuteCloseServer(this.sn.join(','))) // 销毁监听
- this.socket.close()
- this.init()
- this.isLoading = false
- this.isMask = false
- this.maskText = ''
- } else {
- setTimeout(() => {
- this._send()
- }, 3000)
- }
- },
- async getcardStatus() {
- const res = await getcardStatus({ sns: this.sn.join(',') }).then(res => { return res })
- let state = '0'
- if (res.status === 0) {
- state = res.data[0].state
- }
- return state
- },
- // 设置像素
- handleCommand(command) {
- if (this.isLoading || this.isError) {
- return
- }
- this.bitRate = command
- const buffer = ExexutePixel(command, this.sn.join(','))
- this.socket.send(buffer)
- },
- // 音量加减、home键、back事件(keyCode的值分别表示为25减音量,24加音量,4为返回键,3为home键,187为切换菜单)
- handleRoutine(code) {
- if (this.isLoading || this.isError) {
- return
- }
- const buffer = ExexuteKeyDown(code, this.sn.join(','))
- this.socket.send(buffer)
- },
- // 鼠标点击事件
- handleMouseDown(event) {
- if (this.isLoading || this.isError) {
- return
- }
- if (event.button === 0) {
- var posX = this.isRotate ? event.offsetY / VIDEO_HEIGHT * 1920 * 1.0 : event.offsetX / VIDEO_WIDTH * 1080 * 1.0
- var posY = this.isRotate ? (VIDEO_WIDTH - event.offsetX) / VIDEO_WIDTH * 1080 * 1.0 : event.offsetY / VIDEO_HEIGHT * 1920 * 1.0
- var buffer = ExexuteMouseDown(posX.toString(), posY.toString(), this.sn.join(','))
- this.socket.send(buffer)
- this.isDrag = true
- }
- },
- // 鼠标移开事件
- handleMouseLeave(event) {
- if (this.isLoading || this.isError) {
- return
- }
- this.isDrag = false
- var posX = this.isRotate ? event.offsetY / VIDEO_HEIGHT * 1920 * 1.0 : event.offsetX / VIDEO_WIDTH * 1080 * 1.0
- var posY = this.isRotate ? (VIDEO_WIDTH - event.offsetX) / VIDEO_WIDTH * 1080 * 1.0 : event.offsetY / VIDEO_HEIGHT * 1920 * 1.0
- var buffer = ExexuteMouseUp(posX.toString(), posY.toString(), this.sn.join(','))
- this.socket.send(buffer)
- },
- // 鼠标移动事件
- handleMouseMove(event) {
- if (this.isLoading || this.isError) {
- return
- }
- if (this.isDrag && event.button === 0) {
- var posX = this.isRotate ? event.offsetY / VIDEO_HEIGHT * 1920 * 1.0 : event.offsetX / VIDEO_WIDTH * 1080 * 1.0
- var posY = this.isRotate ? (VIDEO_WIDTH - event.offsetX) / VIDEO_WIDTH * 1080 * 1.0 : event.offsetY / VIDEO_HEIGHT * 1920 * 1.0
- var buffer = ExexuteMouseMove(posX.toString(), posY.toString(), this.sn.join(','))
- this.socket.send(buffer)
- }
- },
- // 鼠标离开事件
- handleMouseUp(event) {
- if (this.isLoading || this.isError) {
- return
- }
- this.isDrag = false
- var posX = this.isRotate ? event.offsetY / VIDEO_HEIGHT * 1920 * 1.0 : event.offsetX / VIDEO_WIDTH * 1080 * 1.0
- var posY = this.isRotate ? (VIDEO_WIDTH - event.offsetX) / VIDEO_WIDTH * 1080 * 1.0 : event.offsetY / VIDEO_HEIGHT * 1920 * 1.0
- var buffer = ExexuteMouseUp(posX.toString(), posY.toString(), this.sn.join(','))
- this.socket.send(buffer)
- },
- // 键盘输入事件
- handleKeyDown(event) {
- if (this.isLoading || this.isError) {
- return
- }
- var buffer = ExexuteKeyDown(keycodeMode[event.keyCode] || event.keyCode, this.sn.join(','))
- this.socket.send(buffer)
- },
- // websocket初始化
- init() {
- this.socket = new WebSocket(this.path) // 实例化socket
- this.socket.binaryType = 'arraybuffer'
- this.socket.onopen = this.open // 监听socket连接
- this.socket.onerror = this.onerror // 监听socket错误信息
- this.socket.onmessage = this.getMessage // 监听socket消息
- },
- // websocket连接成功回调
- open() {
- this.isMask = false
- this.isError = false
- this.socket.send(ConfigChannel(this.sn))
- this.socket.onmessage = this.getMessage
- },
- // websocket连接失败回调
- onerror() {
- console.log('websocket连接失败')
- this.isLoading = false
- this.isError = true
- this.maskText = '设备故障或离线'
- },
- getMessage(event) {
- const data = this.parse(event.data) // 分离音视频数据
- const audioData = {
- audio: data.audio,
- video: null,
- duration: data.duration
- }
- // const videoData = {
- // audio: null,
- // video: data.video,
- // duration: data.duration
- // }
- if (this.$refs.audioPlayer && this.$refs.audioPlayer.readyState === 2) {
- const playPromise = this.$refs.audioPlayer.play()
- if (playPromise !== undefined) {
- playPromise.then(() => {
- this.$refs.audioPlayer.play()
- }).catch(() => {})
- }
- }
- if (data.audio != null) { // 喂音频
- this.audioMuxer.feed(audioData)
- }
- if (data.video != null && this.isFeed) { // 喂视频
- this.jmuxer.feed(data)
- }
- if (data.video) {
- if (new Date().getTime() - this.curTime >= 1000) {
- this.fpsCount = 0
- this.curTime = new Date().getTime()
- } else {
- this.fpsCount++
- }
- }
- },
- // 弹窗关闭
- close() {
- this.$emit('closeDialog', 'showMobile')
- },
- // 根据sn获取设备获取清晰度、端口号和ip
- getcardInfoBySn() {
- var list = []
- list.push(this.mobileId)
- getcardInfoBySn({ mobileIdList: list }).then(res => {
- if (res.status === 0) {
- this.ip = res.data[0].ip
- this.port = res.data[0].port
- this.size = res.data[0].size
- this.bitRate = res.data[0].bitRate === '0' ? '1638400' : res.data.bitRate
- this.path = `ws://${res.data[0].ip}:${res.data[0].websocketPort.toString()}`
- //console.log(this.path)
- this.init()
- }
- })
- },
- pause() {
- this.isFeed = false
- },
- audioPlay() {
- if (document.visibilityState === 'visible') {
- this.isFlag = true
- this.socket.send(RequestIFrame(this.sn.join(',')))
- } else {
- this.isFlag = false
- this.isFeed = false
- this.$refs.player.pause()
- }
- },
- ready() {
- this.isLoading = false
- },
- error() {
- this.isMask = true
- this.isError = true
- },
- parse(data) {
- // var input = new Uint8Array(data)
- // var dv = new DataView(input.buffer)
- // var duration = dv.getUint16(0, true) // 获取duration
- // var audioLength = dv.getUint16(2, true)
- // var audio
- // var video
- // if (audioLength === 0) {
- // video = input.subarray(4)
- // } else {
- // audio = input.subarray(4, (audioLength + 4))
- // video = input.subarray(audioLength + 4)
- // }
- // return {
- // audio: audio,
- // video: video,
- // duration: duration
- // }
- var input = new Uint8Array(data)
- var duration
- var video
- var audio
- if (input[0] === 0 && input[1] === 0 && input[2] === 0 && input[3] === 1) {
- video = input
- duration = 24
- var nalType = input[4] & 0x1f
- if (nalType === 0x05 && this.isFlag) { // 策略, 找到sps、sps、或I帧,才继续渲染
- this.isFeed = true
- }
- } else if (input[0] === 0xff) {
- audio = input
- duration = 24
- } else if (input[23] === 0x0b) {
- this.$alert('<div class="c666 fs18 mb10">设备' + this.mobileName + '已在其它端受控</div>', '提示', {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确认',
- customClass: 'cloud-phone-message-box',
- confirmButtonClass: 'cloud-phone-confirm-btn', // 自定义确认按钮样式类名
- callback: action => {
- this.showMobile = false
- }
- })
- } else if (input[0] === 0x68 && input[23] === 0x05) {
- var state = CheckScreenDirection(input.slice(24, 24 + 8))
- if (state === 1) {
- this.isRotate = false
- } else {
- this.isRotate = true
- }
- }
- return {
- audio: audio,
- video: video,
- duration: duration
- }
- }
- }
- }
- </script>
- <style lang="scss">
- .ml111 {margin-left: 111px;}.mt50{margin-top: 50px;}.mt40{margin-top: 40px;}
- .w180h148 {width: 180px;height: 148px;}.mr100{margin-right: 100px;}
- .mobile-dialog {
- width: 417px;
- height: 740px;
- background: #141414;
- box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.5);
- border-radius: 0;
- &.rotate {
- width: 705px;
- height: 452px;
- .el-dialog__body {
- padding: 0;
- .mt226ormt90{
- margin-top: 90px;
- }
- .mlr64ormlr160{
- margin-left: 160px;
- margin-right: 160px;
- }
- .mobile-audio {
- width: 660px;
- height: 372px;
- position: relative;
- video {
- transform:rotate(-90deg);
- position: absolute;
- top: -145px;
- left: 145px;
- }
- .mask {
- width: 100%;
- height: 100%;
- background-color: #fff;
- position: absolute;
- top: 0;
- left: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- }
- }
- .mobile-sidebar {
- width: 45px;
- height: 372px;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .mobile-bottombar {
- width: 100%;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
- }
- .el-dialog__header {
- height: 40px;
- line-height: 40px;
- padding: 0;
- .el-dialog__headerbtn {
- top: 13px;
- right: 15px;
- font-size: 14px;
- .el-dialog__close {
- color: #fff;
- font-weight: 800;
- }
- }
- }
- .el-dialog__body {
- padding: 0;
- .mt226ormt90 {
- margin-top: 226px;
- }
- .mlr64ormlr160{
- margin-left: 64px;
- margin-right: 64px;
- }
- .mobile-audio {
- width: 371px;
- height: 660px;
- position: relative;
- .mask {
- width: 100%;
- height: 100%;
- background-color: #fff;
- position: absolute;
- top: 0;
- left: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- }
- }
- .mobile-sidebar {
- width: 46px;
- height: 660px;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .mobile-bottombar {
- width: 100%;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
- }
- .mobile-dropdown-menu {
- width: 110px;
- padding: 5px 0;
- border: none;
- box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.3);
- .popper__arrow {
- border-width: 10px;
- border-bottom-color: #fff !important;
- top: -8px !important;
- left: 60px !important;
- }
- .el-dropdown-menu__item {
- height: 40px;
- line-height: 40px;
- &:hover {
- background-color: #F3F6FF;
- color: #333;
- }
- }
- }
- audio {
- display: none;
- }
- </style>
|