Browse Source

1.关闭浏览器默认右侧菜单

2.完善左侧菜单
3.新增右键取消选中新增标签功能
4.引入jessibuca.js
master
chendingwei 2 years ago
parent
commit
df96a9931c
  1. 3
      src/App.vue
  2. BIN
      src/assets/images/buildLabelLine.png
  3. 6
      src/store/index.ts
  4. 1
      src/utils/jessibuca-demo/decoder.js
  5. BIN
      src/utils/jessibuca-demo/decoder.wasm
  6. 637
      src/utils/jessibuca-demo/jessibuca.d.ts
  7. 1
      src/utils/jessibuca-demo/jessibuca.js
  8. 51
      src/views/page/Aside/cameraRightMenu.vue
  9. 23
      src/views/page/aside/cameraLeftMenu.vue
  10. 33
      src/views/page/cameraCenter.vue

3
src/App.vue

@ -16,7 +16,10 @@ import MqttUnit from "@/utils/mqttunit";
import { onMounted } from 'vue';
import {WebRtcStreamer} from '@/utils/webrtc-streamer/webrtcstreamer.js'
let pinia = useStore()
onMounted(() => {
//
document.oncontextmenu = new Function("event.returnValue=false");
let client = new MqttUnit().mqttInit();
let WebRtcStreamerRt=WebRtcStreamer()
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {

BIN
src/assets/images/buildLabelLine.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

6
src/store/index.ts

@ -10,7 +10,8 @@ export const useStore = defineStore('Index', {
audioCount: 0,
audioCurrentCount: 0,
editType: 0,
addLabel: {labelType:"",isAddLabel: false}
addLabel: {cmMarkGroupId:0,labelType:"",isAddLabel: false},
curTreeKey:""
}
},
@ -28,6 +29,9 @@ export const useStore = defineStore('Index', {
updateCurSelectKey(key: string) {
this.curSelectKey = key
},
updateCurTreeKey(key: string) {
this.curTreeKey = key
},
updateaudioCurrentCount(num: number) {
this.audioCurrentCount = num
},

1
src/utils/jessibuca-demo/decoder.js

File diff suppressed because one or more lines are too long

BIN
src/utils/jessibuca-demo/decoder.wasm

Binary file not shown.

637
src/utils/jessibuca-demo/jessibuca.d.ts

@ -0,0 +1,637 @@
declare namespace Jessibuca {
/** 超时信息 */
enum TIMEOUT {
/** 当play()的时候,如果没有数据返回 */
loadingTimeout = 'loadingTimeout',
/** 当播放过程中,如果超过timeout之后没有数据渲染 */
delayTimeout = 'delayTimeout',
}
/** 错误信息 */
enum ERROR {
/** 播放错误,url 为空的时候,调用 play 方法 */
playError = 'playError',
/** http 请求失败 */
fetchError = 'fetchError',
/** websocket 请求失败 */
websocketError = 'websocketError',
/** webcodecs 解码 h265 失败 */
webcodecsH265NotSupport = 'webcodecsH265NotSupport',
/** mediaSource 解码 h265 失败 */
mediaSourceH265NotSupport = 'mediaSourceH265NotSupport',
/** wasm 解码失败 */
wasmDecodeError = 'wasmDecodeError',
}
interface Config {
/**
*
* * string document.getElementById('id')
* */
container: HTMLElement | string;
/**
*
*/
videoBuffer?: number;
/**
* worker地址
* * decoder.js文件 decoder.js decoder.wasm文件必须是放在同一个目录下面 */
decoder?: string;
/**
* 使
*/
forceNoOffscreen?: boolean;
/**
* 'visibilityState''hidden'
*/
hiddenAutoPause?: boolean;
/**
* `false`
*/
hasAudio?: boolean;
/**
* 0()180270
*/
rotate?: boolean;
/**
* 1. `true`,canvas区域,, `setScaleMode(1)`
* 2. `false`canvas区域, `setScaleMode(0)`
*/
isResize?: boolean;
/**
* 1. `true`,canvas区域,,, `setScaleMode(2)`
*/
isFullResize?: boolean;
/**
* 1. `true`ws协议不检验是否以.flv为依据
*/
isFlv?: boolean;
/**
*
*/
debug?: boolean;
/**
* 1. ,
* 2. (loading)(heart),,timeout事件
*/
timeout?: number;
/**
* 1. ,
* 2. ,,timeout事件
*/
heartTimeout?: number;
/**
* 1. ,
* 2. ,,timeout事件
*/
loadingTimeout?: number;
/**
*
*/
supportDblclickFullscreen?: boolean;
/**
*
*/
showBandwidth?: boolean;
/**
*
*/
operateBtns?: {
/** 是否显示全屏按钮 */
fullscreen?: boolean;
/** 是否显示截图按钮 */
screenshot?: boolean;
/** 是否显示播放暂停按钮 */
play?: boolean;
/** 是否显示声音按钮 */
audio?: boolean;
/** 是否显示录制按 */
record?: boolean;
};
/**
* , canvas标签渲染视频并不会像video标签那样保持屏幕常亮
*/
keepScreenOn?: boolean;
/**
*
*/
isNotMute?: boolean;
/**
*
*/
loadingText?: string;
/**
*
*/
background?: string;
/**
* MediaSource硬解码
* * H.264Safari on iOS不支持
* * forceNoOffscreen false ()
*/
useMSE?: boolean;
/**
* Webcodecs硬解码
* * H.264 (chrome 94https或者localhost环境)
* * forceNoOffscreen false )
* */
useWCS?: boolean;
/**
*
* esc -> 退arrowUp -> arrowDown ->
*/
hotKey?: boolean;
/**
* 使MSE或者Webcodecs H265的时候wasm模式
* false Error true wasm模式播放
*/
autoWasm?: boolean;
/**
* heartTimeout ,
*/
heartTimeoutReplay?: boolean,
/**
* heartTimeoutReplay
*/
heartTimeoutReplayTimes?: number,
/**
* loadingTimeout loading之后自动再播放,
*/
loadingTimeoutReplay?: boolean,
/**
* heartTimeoutReplay
*/
loadingTimeoutReplayTimes?: number
/**
* wasm解码报错之后
*/
wasmDecodeErrorReplay?: boolean,
/**
* https://github.com/langhuihui/jessibuca/issues/152 解决方案
* WebGL图像预处理默认每次取4字节的数据540x960分辨率下的UV分量宽度是540/2=2704绿
*/
openWebglAlignment?: boolean
}
}
declare class Jessibuca {
constructor(config?: Jessibuca.Config);
/**
*
@example
// 开启
jessibuca.setDebug(true)
// 关闭
jessibuca.setDebug(false)
*/
setDebug(flag: boolean): void;
/**
*
@example
jessibuca.mute()
*/
mute(): void;
/**
*
@example
jessibuca.cancelMute()
*/
cancelMute(): void;
/**
*
*
* iPhonechrome等要求自动播放时使
*
* https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
*/
audioResume(): void;
/**
*
* ,
* ,,timeout事件
@example
jessibuca.setTimeout(10)
jessibuca.on('timeout',function(){
//
});
*/
setTimeout(): void;
/**
* @param mode
* 0 canvas区域, `isResize` false
*
* 1 ,canvas区域,, `isResize` true
*
* 2 ,canvas区域,,, `isFullResize` true
@example
jessibuca.setScaleMode(0)
jessibuca.setScaleMode(1)
jessibuca.setScaleMode(2)
*/
setScaleMode(mode: number): void;
/**
*
*
* pause `play()`
@example
jessibuca.pause().then(()=>{
console.log('pause success')
jessibuca.play().then(()=>{
}).catch((e)=>{
})
}).catch((e)=>{
console.log('pause error',e);
})
*/
pause(): Promise<void>;
/**
* ,
@example
jessibuca.close();
*/
close(): void;
/**
*
@example
jessibuca.destroy()
*/
destroy(): void;
/**
*
@example
jessibuca.clearView()
*/
clearView(): void;
/**
*
@example
jessibuca.play('url').then(()=>{
console.log('play success')
}).catch((e)=>{
console.log('play error',e)
})
//
jessibuca.play()
*/
play(url?: string): Promise<void>;
/**
*
*/
resize(): void;
/**
*
*
* `videoBuffer`
*
@example
// 设置 200ms 缓冲
jessibuca.setBufferTime(0.2)
*/
setBufferTime(time: number): void;
/**
* 0() 180270
*
* > iOS没有全屏API *
@example
jessibuca.setRotate(0)
jessibuca.setRotate(90)
jessibuca.setRotate(270)
*/
setRotate(deg: number): void;
/**
*
* 0 1
*
* > mute cancelMute setVolume(0) mute方法mute setVolume(0)0
* @param volume 0;1
@example
jessibuca.setVolume(0.2)
jessibuca.setVolume(0)
jessibuca.setVolume(1)
*/
setVolume(volume: number): void;
/**
*
@example
var result = jessibuca.hasLoaded()
console.log(result) // true
*/
hasLoaded(): boolean;
/**
* , canvas标签渲染视频并不会像video标签那样保持屏幕常亮
* H5目前在chrome\edge 84, android chrome 84API, https页面
*
@example
jessibuca.setKeepScreenOn()
*/
setKeepScreenOn(): boolean;
/**
* ()
@example
jessibuca.setFullscreen(true)
//
jessibuca.setFullscreen(false)
*/
setFullscreen(flag: boolean): void;
/**
*
*
* @param filename , , `时间戳`
* @param format , png或jpeg或者webp , `png`
* @param quality , jpeg或者webp时0 ~ 1 , `0.92`
* @param type , download或者base64或者blob`download`
@example
jessibuca.screenshot("test","png",0.5)
const base64 = jessibuca.screenshot("test","png",0.5,'base64')
const fileBlob = jessibuca.screenshot("test",'blob')
*/
screenshot(filename?: string, format?: string, quality?: number, type?: string): void;
/**
*
* @param fileName
* @param fileType webmwebm mp4
@example
jessibuca.startRecord('xxx','webm')
*/
startRecord(fileName: string, fileType: string): void;
/**
*
@example
jessibuca.stopRecordAndSave()
*/
stopRecordAndSave(): void;
/**
*
@example
var result = jessibuca.isPlaying()
console.log(result) // true
*/
isPlaying(): boolean;
/**
*
@example
var result = jessibuca.isMute()
console.log(result) // true
*/
isMute(): boolean;
/**
*
@example
var result = jessibuca.isRecording()
console.log(result) // true
*/
isRecording(): boolean;
/**
* jessibuca
* @example
* jessibuca.on("load",function(){console.log('load')})
*/
on(event: 'load', callback: () => void): void;
/**
* ms
* @example
* jessibuca.on('timeUpdate',function (ts) {console.log('timeUpdate',ts);})
*/
on(event: 'timeUpdate', callback: () => void): void;
/**
* 2
* @example
* jessibuca.on("videoInfo",function(data){console.log('width:',data.width,'height:',data.width)})
*/
on(event: 'videoInfo', callback: (data: {
/** 视频宽 */
width: number;
/** 视频高 */
height: number;
}) => void): void;
/**
* 2
* @example
* jessibuca.on("audioInfo",function(data){console.log('numOfChannels:',data.numOfChannels,'sampleRate',data.sampleRate)})
*/
on(event: 'audioInfo', callback: (data: {
/** 声频通道 */
numOfChannels: number;
/** 采样率 */
sampleRate: number;
}) => void): void;
/**
*
* @example
* jessibuca.on("log",function(data){console.log('data:',data)})
*/
on(event: 'log', callback: () => void): void;
/**
*
* @example
* jessibuca.on("error",function(error){
if(error === Jessibuca.ERROR.fetchError){
//
}
else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){
//
}
console.log('error:',error)
})
*/
on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void;
/**
* KB 1,
* @example
* jessibuca.on("kBps",function(data){console.log('kBps:',data)})
*/
on(event: 'kBps', callback: (value: number) => void): void;
/**
*
* @example
* jessibuca.on("start",function(){console.log('start render')})
*/
on(event: 'start', callback: () => void): void;
/**
* ,
* @example
* jessibuca.on("timeout",function(error){console.log('timeout:',error)})
*/
on(event: 'timeout', callback: (error: Jessibuca.TIMEOUT) => void): void;
/**
* play()
* @example
* jessibuca.on("loadingTimeout",function(){console.log('timeout')})
*/
on(event: 'loadingTimeout', callback: () => void): void;
/**
* timeout之后没有数据渲染
* @example
* jessibuca.on("delayTimeout",function(){console.log('timeout')})
*/
on(event: 'delayTimeout', callback: () => void): void;
/**
*
* @example
* jessibuca.on("fullscreen",function(flag){console.log('is fullscreen',flag)})
*/
on(event: 'fullscreen', callback: () => void): void;
/**
*
* @example
* jessibuca.on("play",function(flag){console.log('play')})
*/
on(event: 'play', callback: () => void): void;
/**
*
* @example
* jessibuca.on("pause",function(flag){console.log('pause')})
*/
on(event: 'pause', callback: () => void): void;
/**
* boolean值
* @example
* jessibuca.on("mute",function(flag){console.log('is mute',flag)})
*/
on(event: 'mute', callback: () => void): void;
/**
* 1
* @example
* jessibuca.on("stats",function(s){console.log("stats is",s)})
*/
on(event: 'stats', callback: (stats: {
/** 当前缓冲区时长,单位毫秒 */
buf: number;
/** 当前视频帧率 */
fps: number;
/** 当前音频码率,单位bit */
abps: number;
/** 当前视频码率,单位bit */
vbps: number;
/** 当前视频帧pts,单位毫秒 */
ts: number;
}) => void): void;
/**
* 1
* @param performance 0: 表示卡顿,1: 表示流畅,2: 表示非常流程
* @example
* jessibuca.on("performance",function(performance){console.log("performance is",performance)})
*/
on(event: 'performance', callback: (performance: 0 | 1 | 2) => void): void;
/**
*
* @example
* jessibuca.on("recordStart",function(){console.log("record start")})
*/
on(event: 'recordStart', callback: () => void): void;
/**
*
* @example
* jessibuca.on("recordEnd",function(){console.log("record end")})
*/
on(event: 'recordEnd', callback: () => void): void;
/**
* 1s一次
* @example
* jessibuca.on("recordingTimestamp",function(timestamp){console.log("recordingTimestamp is",timestamp)})
*/
on(event: 'recordingTimestamp', callback: (timestamp: number) => void): void;
/**
* play方法 -> -> -> ->
* @param event
* @param callback
*/
on(event: 'playToRenderTimes', callback: (times: {
playInitStart: number, // 1 初始化
playStart: number, // 2 初始化
streamStart: number, // 3 网络请求
streamResponse: number, // 4 网络请求
demuxStart: number, // 5 解封装
decodeStart: number, // 6 解码
videoStart: number, // 7 渲染
playTimestamp: number,// playStart- playInitStart
streamTimestamp: number,// streamStart - playStart
streamResponseTimestamp: number,// streamResponse - streamStart
demuxTimestamp: number, // demuxStart - streamResponse
decodeTimestamp: number, // decodeStart - demuxStart
videoTimestamp: number,// videoStart - decodeStart
allTimestamp: number // videoStart - playInitStart
}) => void): void
/**
*
*
@example
jessibuca.on("load",function(){console.log('load')})
*/
on(event: string, callback: Function): void;
}
export default Jessibuca;

1
src/utils/jessibuca-demo/jessibuca.js

File diff suppressed because one or more lines are too long

51
src/views/page/Aside/cameraRightMenu.vue

@ -3,8 +3,9 @@
<Popover placement="left" trigger="hover">
<template #content>
<ul>
<p @click="addLabelFn('视频标签')">视频标签</p>
<p @click="addLabelFn('建筑标签')">建筑标签</p>
<!-- <p @click="addLabelFn('视频标签')">视频标签</p>
<p @click="addLabelFn('建筑标签')">建筑标签</p> -->
<p class="labelList" v-for="item in labelList" :key="item.id" @click="addLabelFn(item.name,item.id)">{{ item.name }}</p>
<!-- <p></p> -->
</ul>
</template>
@ -31,13 +32,7 @@
<div class="buttomName">设置</div>
</div>
</div>
<a-modal
v-model:visible="visible"
title="用户设置"
width="90%"
wrapClassName="full-modal"
:footer="null"
>
<a-modal v-model:visible="visible" title="用户设置" width="90%" wrapClassName="full-modal" :footer="null">
<userEdit></userEdit>
</a-modal>
@ -45,18 +40,31 @@
<script setup lang='ts'>
import userEdit from '@/views/page/aside/rightMenuItem/userEdit.vue'
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import { Popover } from 'ant-design-vue';
import { useStore } from '@/store/index';
import * as markGroupApi from '@/axios/cameraMark/markGroupApi';
const visible = ref(false)
const labelList :any=ref([])
function openUserEdit() {
visible.value = !visible.value
}
let piniaStore = useStore();
function addLabelFn(str: string) {
piniaStore.updateIsAddLabel({ labelType: str, isAddLabel: true })
function addLabelFn(str: string,id:number) {
piniaStore.updateIsAddLabel({ cmMarkGroupId:id,labelType: str, isAddLabel: true })
}
function init() {
markGroupApi.GetList().then((res: any) => {
console.log(res, 'markGroupApi');
let result = res.data
if (result.code == 200) {
labelList.value=result.data
}
})
}
onMounted(() => {
init()
})
</script>
<style scoped lang='less'>
@ -66,7 +74,9 @@ function addLabelFn(str: string) {
top: 15%;
width: 80px;
}
.labelList{
cursor: pointer;
}
@keyframes rounte {
from {
transform: rotate(0deg);
@ -113,18 +123,17 @@ function addLabelFn(str: string) {
z-index: 2;
font-size: 0.8em;
}
ul{
ul {
padding: 0;
margin: 0;
p:last-child{
p:last-child {
margin: 0;
}
}
}
</style>
<style lang="less" >
.full-modal {
.ant-modal {
max-width: 100%;
@ -132,11 +141,13 @@ ul{
padding-bottom: 0;
margin: auto;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(90vh);
}
.ant-modal-body {
flex: 1;
}

23
src/views/page/aside/cameraLeftMenu.vue

@ -73,7 +73,7 @@ let searchStr = ref('');
const expandedKeys = ref<any[]>([]);
onMounted(() => {
loadTreeData()
init()
})
//
watch(searchStr, (newVal, oldVal) => {
@ -91,9 +91,12 @@ watch(searchStr, (newVal, oldVal) => {
expandedKeys.value = expandedKeyArr
})
//
function selectCamera(e: any, e2: any) {
if (!e2.node.isGroup) {
piniaStore.updateCurSelectKey(e2.node.cbCameraId.toString())
function selectCamera(treeKeyArr: any, item: any) {
if (treeKeyArr.length != 0) {
piniaStore.updateCurTreeKey(treeKeyArr[0].toString())
}
if (!item.node.isGroup) {
piniaStore.updateCurSelectKey(item.node.cbCameraId.toString())
}
}
//
@ -115,25 +118,31 @@ function searchFn(newVal: string, arr: any[]): any {
});
return expandedKeyArr
}
function loadTreeData() {
function loadCameraData() {
cameraApi.GetList().then((res: any) => {
let list: Array<any> = res.data.data;
piniaStore.addCameraMap(list)
});
}
function loadTreeData() {
cameraApi.GetTreeList().then((res: any) => {
console.log(res);
if (res.data.code == 200) {
treeData.value = againTreeData(res.data.data)
console.log(treeData.value[0] );
console.log(treeData.value[0]);
}
})
}
function init() {
loadCameraData()
loadTreeData()
}
//
function againTreeData(arr: any) {
let expandedArr: any = []
arr.forEach((item: any, index: number) => {
expandedArr[index]={}
expandedArr[index] = {}
if (item.isGroup) {
expandedArr[index].children = againTreeData(item.child)
}

33
src/views/page/cameraCenter.vue

@ -25,17 +25,20 @@
</template>
</a-dropdown>
</div>
<div class="labels" v-if="addLabel.isAddLabel"
:style="`position: fixed;transform: translate(-50%,-100%);top:${addLabelTop}px;left:${addLabelLeft}px`">
<div class="labels" v-if="addLabel.isAddLabel && addLabel.labelType == '建筑标签'"
:style="`pointer-events: none;transform: translate(-10px,-10px);position: fixed;top:${addLabelTop}px;left:${addLabelLeft}px`">
<div class="labels-item" v-if="addLabel.labelType == '建筑标签'" alt="">
<div class="labels-item" alt="">
<img src="@/assets/images/buildLabelLine.png">
<div>
<img src="@/assets/images/buildLabelBg.png">
<div class="label-content">{{ addLabel.labelType }}</div>
</div>
</div>
<div class="labels-item" v-else-if="addLabel.labelType == '视频标签'">
</div>
<div class="labels" v-if="addLabel.isAddLabel && addLabel.labelType == '视频标签'"
:style="`position: fixed;transform: translate(-50%,-100%);top:${addLabelTop}px;left:${addLabelLeft}px`">
<div class="labels-item">
<!-- <div class="labels-item" v-if="item.inFlag"> -->
<div>
<img src="@/assets/images/dialog.png" alt="">
@ -193,11 +196,13 @@ function mouseOutVideo() {
player.classList.remove('activeChoose');
}
function mouseDownVideo(e: MouseEvent) {
console.log('mouseDownVideo');
console.log('mouseDownVideo', e.buttons);
if (!isActiveChoose.value) return;
// e.buttons == 1
if (e.buttons == 1) {
let cameraId = cameraMap.value.get(curSelectKey.value).id;
let name = "1";
let name = addLabel.value.labelType;
let videoWidth = player.videoWidth | 1920;
let videoHeight = player.videoHeight | 1080;
let canvasWidth = player.clientWidth;
@ -212,7 +217,8 @@ function mouseDownVideo(e: MouseEvent) {
VideoWidth: videoWidth,
VideoHeight: videoHeight,
CanvasLeftRatio: canvasLeftRatio,
CanvasTopRatio: canvasTopRatio
CanvasTopRatio: canvasTopRatio,
CmMarkGroupId:addLabel.value.cmMarkGroupId
}
markLabelApi.AddReturnId({
'entity': entity
@ -242,10 +248,15 @@ function mouseDownVideo(e: MouseEvent) {
}
labelList.value.push(obj)
isActiveChoose.value = false
piniaStore.updateIsAddLabel({ labelType: "", isAddLabel: false })
piniaStore.updateIsAddLabel({ cmMarkGroupId:0,labelType: "", isAddLabel: false })
}
});
})
} else {
//
isActiveChoose.value = false
piniaStore.updateIsAddLabel({ cmMarkGroupId:0,labelType: "", isAddLabel: false })
}
}
function switchCamera(cameraId: string) {
@ -306,8 +317,9 @@ function getRtspUrl(cameraObj: any): string {
return `rtsp://${cameraObj.userName}:${cameraObj.password}@${cameraObj.ip}:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1`;
}
function getLabel(cbCameraId: string | number) {
markLabelApi.GetList({ cbCameraId }).then((res: any) => {
console.log(res, 'res');
let str=JSON.stringify({ cbCameraId })
markLabelApi.GetList({queryJson:str}).then((res: any) => {
console.log(res, 'resmarkLabelApi');
if (res.data.code == 200) {
console.log(res, 'res');
labelList.value = res.data.data
@ -398,7 +410,6 @@ onUnmounted(() => {
position: absolute;
top: 0;
left: 0;
transform: translate(-50%, -100%);
z-index: 2;
.labels-item {

Loading…
Cancel
Save