支持拉框放大和拉框缩小

This commit is contained in:
lin 2026-06-11 17:29:20 +08:00
parent 3417244705
commit 70bc01bd90
18 changed files with 634 additions and 158 deletions

View File

@ -170,7 +170,7 @@ public class DeviceControl {
@Parameter(name = "lengthx", description = "拉框长度像素值", required = true) @Parameter(name = "lengthx", description = "拉框长度像素值", required = true)
@Parameter(name = "lengthy", description = "拉框宽度像素值", required = true) @Parameter(name = "lengthy", description = "拉框宽度像素值", required = true)
@GetMapping("drag_zoom/zoom_in") @GetMapping("drag_zoom/zoom_in")
public DeferredResult<WVPResult<String>> dragZoomIn(@RequestParam String deviceId, String channelId, public void dragZoomIn(@RequestParam String deviceId, String channelId,
@RequestParam int length, @RequestParam int length,
@RequestParam int width, @RequestParam int width,
@RequestParam int midpointx, @RequestParam int midpointx,
@ -182,28 +182,20 @@ public class DeviceControl {
} }
Device device = deviceService.getDeviceByDeviceId(deviceId); Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在"); Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<String>> result = new DeferredResult<>(); deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy);
deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy, (code, msg, data) -> {
result.setResult(new WVPResult<>(code, msg, data));
});
result.onTimeout(() -> {
log.warn("[设备拉框放大] 操作超时, 设备未返回应答指令, {}", deviceId);
result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答"));
});
return result;
} }
@Operation(summary = "拉框缩小", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "拉框缩小", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号") @Parameter(name = "channelId", description = "通道国标编号")
@Parameter(name = "length", description = "播放窗口长度像素值", required = true) @Parameter(name = "length", description = "播放窗口长度像素值", required = true)
@Parameter(name = "width", description = "拉框中心的横轴坐标像素值", required = true) @Parameter(name = "width", description = "播放窗口宽像素值", required = true)
@Parameter(name = "midpointx", description = "拉框中心的横轴坐标像素值", required = true) @Parameter(name = "midpointx", description = "拉框中心的横轴坐标像素值", required = true)
@Parameter(name = "midpointy", description = "拉框中心的纵轴坐标像素值", required = true) @Parameter(name = "midpointy", description = "拉框中心的纵轴坐标像素值", required = true)
@Parameter(name = "lengthx", description = "拉框长度像素值", required = true) @Parameter(name = "lengthx", description = "拉框长度像素值", required = true)
@Parameter(name = "lengthy", description = "拉框宽度像素值", required = true) @Parameter(name = "lengthy", description = "拉框宽度像素值", required = true)
@GetMapping("/drag_zoom/zoom_out") @GetMapping("/drag_zoom/zoom_out")
public DeferredResult<WVPResult<String>> dragZoomOut(@RequestParam String deviceId, public void dragZoomOut(@RequestParam String deviceId,
@RequestParam(required = false) String channelId, @RequestParam(required = false) String channelId,
@RequestParam int length, @RequestParam int length,
@RequestParam int width, @RequestParam int width,
@ -217,14 +209,6 @@ public class DeviceControl {
} }
Device device = deviceService.getDeviceByDeviceId(deviceId); Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在"); Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<String>> result = new DeferredResult<>(); deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy);
deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx,lengthy, (code, msg, data) -> {
result.setResult(new WVPResult<>(code, msg, data));
});
result.onTimeout(() -> {
log.warn("[设备拉框放大] 操作超时, 设备未返回应答指令, {}", deviceId);
result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "操作超时, 设备未应答"));
});
return result;
} }
} }

View File

@ -185,9 +185,9 @@ public interface IDeviceService {
void homePosition(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback); void homePosition(Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback);
void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback); void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy);
void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback); void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy);
void deviceStatus(Device device, ErrorCallback<String> callback); void deviceStatus(Device device, ErrorCallback<String> callback);

View File

@ -1276,7 +1276,7 @@ public class DeviceServiceImpl implements IDeviceService {
} }
@Override @Override
public void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback) { public void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy) {
if (!userSetting.getServerId().equals(device.getServerId())) { if (!userSetting.getServerId().equals(device.getServerId())) {
redisRpcService.dragZoomIn(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy); redisRpcService.dragZoomIn(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy);
return; return;
@ -1292,16 +1292,15 @@ public class DeviceServiceImpl implements IDeviceService {
cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n"); cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n");
cmdXml.append("</DragZoomIn>\r\n"); cmdXml.append("</DragZoomIn>\r\n");
try { try {
sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback); sipCommander.dragZoomCmd(device, channelId, cmdXml.toString());
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 拉框放大: {}", e.getMessage()); log.error("[命令发送失败] 拉框放大: {}", e.getMessage());
callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
} }
} }
@Override @Override
public void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback) { public void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy) {
if (!userSetting.getServerId().equals(device.getServerId())) { if (!userSetting.getServerId().equals(device.getServerId())) {
redisRpcService.dragZoomOut(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy); redisRpcService.dragZoomOut(device.getServerId(), device, channelId, length, width, midpointx, midpointy, lengthx, lengthy);
return; return;
@ -1317,10 +1316,9 @@ public class DeviceServiceImpl implements IDeviceService {
cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n"); cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n");
cmdXml.append("</DragZoomOut>\r\n"); cmdXml.append("</DragZoomOut>\r\n");
try { try {
sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback); sipCommander.dragZoomCmd(device, channelId, cmdXml.toString());
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 拉框放大: {}", e.getMessage()); log.error("[命令发送失败] 拉框放大: {}", e.getMessage());
callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
} }
} }

View File

@ -293,7 +293,7 @@ public interface ISIPCommander {
* @param channelId 通道id * @param channelId 通道id
* @param cmdString 前端控制指令串 * @param cmdString 前端控制指令串
*/ */
void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException; void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException;
void playbackControlCmd(Device device, DeviceChannel channel, String stream, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; void playbackControlCmd(Device device, DeviceChannel channel, String stream, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;

View File

@ -1292,7 +1292,7 @@ public class SIPCommander implements ISIPCommander {
} }
@Override @Override
public void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException { public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
String cmdType = "DeviceControl"; String cmdType = "DeviceControl";
int sn = (int) ((Math.random() * 9 + 1) * 100000); int sn = (int) ((Math.random() * 9 + 1) * 100000);
@ -1311,9 +1311,6 @@ public class SIPCommander implements ISIPCommander {
dragXml.append(cmdString); dragXml.append(cmdString);
dragXml.append("</Control>\r\n"); dragXml.append("</Control>\r\n");
MessageEvent<String> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback);
messageSubscribe.addSubscribe(messageEvent);
Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
} }

View File

@ -390,9 +390,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
cmdXml.append("<LengthX>" + dragZoom.getLengthX() + "</LengthX>\r\n"); cmdXml.append("<LengthX>" + dragZoom.getLengthX() + "</LengthX>\r\n");
cmdXml.append("<LengthY>" + dragZoom.getLengthY() + "</LengthY>\r\n"); cmdXml.append("<LengthY>" + dragZoom.getLengthY() + "</LengthY>\r\n");
cmdXml.append("</" + type.getVal() + ">\r\n"); cmdXml.append("</" + type.getVal() + ">\r\n");
cmder.dragZoomCmd(device, deviceChannel.getDeviceId(), cmdXml.toString(), (code, msg, data) -> { cmder.dragZoomCmd(device, deviceChannel.getDeviceId(), cmdXml.toString());
});
responseAck(request, Response.OK); responseAck(request, Response.OK);
} catch (Exception e) { } catch (Exception e) {
log.error("[命令发送失败] 拉框控制: {}", e.getMessage()); log.error("[命令发送失败] 拉框控制: {}", e.getMessage());

View File

@ -332,18 +332,15 @@ public class RedisRpcDeviceController extends RpcController {
return response; return response;
} }
try { try {
deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> { deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy);
response.setStatusCode(ErrorCode.SUCCESS.getCode());
response.setBody(new WVPResult<>(code, msg, data));
// 手动发送结果
sendResponse(response);
});
}catch (ControllerException e) { }catch (ControllerException e) {
response.setStatusCode(e.getCode()); response.setStatusCode(e.getCode());
response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg()));
sendResponse(response); return response;
} }
return null; response.setStatusCode(ErrorCode.SUCCESS.getCode());
response.setBody(WVPResult.success());
return response;
} }
@RedisRpcMapping("dragZoomOut") @RedisRpcMapping("dragZoomOut")
@ -367,18 +364,15 @@ public class RedisRpcDeviceController extends RpcController {
return response; return response;
} }
try { try {
deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> { deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy);
response.setStatusCode(ErrorCode.SUCCESS.getCode());
response.setBody(new WVPResult<>(code, msg, data));
// 手动发送结果
sendResponse(response);
});
}catch (ControllerException e) { }catch (ControllerException e) {
response.setStatusCode(e.getCode()); response.setStatusCode(e.getCode());
response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg())); response.setBody(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMsg()));
sendResponse(response); return response;
} }
return null; response.setStatusCode(ErrorCode.SUCCESS.getCode());
response.setBody(WVPResult.success());
return response;
} }
@RedisRpcMapping("alarm") @RedisRpcMapping("alarm")

View File

@ -283,3 +283,19 @@ export function getRegisterTimeStatistics({ deviceId, count }) {
}) })
} }
export function dragZoomIn(params) {
return request({
method: 'get',
url: '/api/device/control/drag_zoom/zoom_in',
params
})
}
export function dragZoomOut(params) {
return request({
method: 'get',
url: '/api/device/control/drag_zoom/zoom_out',
params
})
}

204
web/src/mixins/dragZoom.js Normal file
View File

@ -0,0 +1,204 @@
export default {
data() {
return {
dragGridEnabled: true,
overlayCanvas: null,
overlayCtx: null,
dragActive: false,
dragStart: null,
dragCurrent: null,
dragVideoRect: null,
dragCallback: null
}
},
computed: {
dragRect() {
if (!this.dragStart || !this.dragCurrent) return null
return {
left: Math.min(this.dragStart.x, this.dragCurrent.x),
top: Math.min(this.dragStart.y, this.dragCurrent.y),
width: Math.abs(this.dragCurrent.x - this.dragStart.x),
height: Math.abs(this.dragCurrent.y - this.dragStart.y)
}
},
dragInfo() {
if (!this.dragRect) return null
return {
midX: Math.round(this.dragRect.left + this.dragRect.width / 2),
midY: Math.round(this.dragRect.top + this.dragRect.height / 2),
width: Math.round(this.dragRect.width),
height: Math.round(this.dragRect.height)
}
}
},
beforeDestroy() {
this._removeCanvas()
},
methods: {
getVideoElement() {
return null
},
_ensureCanvas() {
this._removeCanvas()
const videoRect = this.getVideoRect()
if (!videoRect) return null
const parentRect = this.$el.getBoundingClientRect()
const w = Math.round(videoRect.width)
const h = Math.round(videoRect.height)
const canvas = document.createElement('canvas')
canvas.style.position = 'absolute'
canvas.style.left = (videoRect.left - parentRect.left) + 'px'
canvas.style.top = (videoRect.top - parentRect.top) + 'px'
canvas.style.width = w + 'px'
canvas.style.height = h + 'px'
canvas.width = w
canvas.height = h
canvas.style.zIndex = '999'
canvas.style.pointerEvents = 'none'
console.log('this.dragGridEnabled ' + this.dragGridEnabled)
if (this.dragGridEnabled) {
console.log('加载网格背景')
canvas.style.backgroundImage =
'linear-gradient(rgba(64, 158, 255, 0.3) 1px, transparent 2px),' +
'linear-gradient(90deg, rgba(64, 158, 255, 0.3) 1px, transparent 2px)'
canvas.style.backgroundSize = '25px 25px'
canvas.style.border = '2px solid #409EFF'
}
this.$el.appendChild(canvas)
console.log(this.$el)
const ctx = canvas.getContext('2d')
this.overlayCanvas = canvas
this.overlayCtx = ctx
return { canvas, ctx }
},
_removeCanvas() {
this._unbindDragEvents()
if (this.overlayCanvas && this.overlayCanvas.parentNode) {
this.overlayCanvas.parentNode.removeChild(this.overlayCanvas)
}
this.overlayCanvas = null
this.overlayCtx = null
},
_bindDragEvents() {
const c = this.overlayCanvas
if (!c) return
c.style.pointerEvents = 'auto'
c.style.cursor = 'crosshair'
c.addEventListener('mousedown', this._onDragMouseDown)
c.addEventListener('mousemove', this._onDragMove)
c.addEventListener('mouseup', this._onDragEnd)
c.addEventListener('mouseleave', this._onDragEnd)
},
_unbindDragEvents() {
const c = this.overlayCanvas
if (!c) return
c.style.pointerEvents = 'none'
c.style.cursor = 'default'
c.removeEventListener('mousedown', this._onDragMouseDown)
c.removeEventListener('mousemove', this._onDragMove)
c.removeEventListener('mouseup', this._onDragEnd)
c.removeEventListener('mouseleave', this._onDragEnd)
},
_drawOverlay() {
const ctx = this.overlayCtx
const canvas = this.overlayCanvas
if (!ctx || !canvas) return
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (this.dragRect) {
this._drawDragRect(ctx)
}
},
_drawDragRect(ctx) {
const r = this.dragRect
if (!r) return
ctx.strokeStyle = '#409EFF'
ctx.lineWidth = 2
ctx.setLineDash([6, 3])
ctx.fillStyle = 'rgba(64, 158, 255, 0.15)'
ctx.beginPath()
ctx.rect(r.left, r.top, r.width, r.height)
ctx.fill()
ctx.stroke()
ctx.setLineDash([])
const info = this.dragInfo
if (!info) return
const text = '\u4E2D\u5FC3: (' + info.midX + ', ' + info.midY + ') \u5927\u5C0F: ' + info.width + ' \u00D7 ' + info.height
ctx.font = '12px sans-serif'
const textW = ctx.measureText(text).width
const labelW = textW + 16
const labelH = 22
const labelX = r.left
const labelY = r.top + r.height + 6
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
ctx.fillRect(labelX, labelY, labelW, labelH)
ctx.fillStyle = '#fff'
ctx.fillText(text, labelX + 8, labelY + 15)
},
startDragZoom(callback) {
this._ensureCanvas()
this._bindDragEvents()
this.dragCallback = callback || null
this.dragActive = true
this.dragStart = null
this.dragCurrent = null
this.dragVideoRect = null
},
_onDragMouseDown(e) {
if (!this.dragActive) return
e.preventDefault()
const videoRect = this.getVideoRect()
if (!videoRect) return
this.dragVideoRect = videoRect
this.dragStart = {
x: e.clientX - videoRect.left,
y: e.clientY - videoRect.top
}
this.dragCurrent = { ...this.dragStart }
this._drawOverlay()
},
_onDragMove(e) {
if (!this.dragActive || !this.dragStart || !this.dragVideoRect) return
e.preventDefault()
this.dragCurrent = {
x: e.clientX - this.dragVideoRect.left,
y: e.clientY - this.dragVideoRect.top
}
this._drawOverlay()
},
_onDragEnd() {
if (!this.dragActive) return
if (!this.dragStart || !this.dragCurrent) return
const sx = Math.min(this.dragStart.x, this.dragCurrent.x)
const sy = Math.min(this.dragStart.y, this.dragCurrent.y)
const ex = Math.max(this.dragStart.x, this.dragCurrent.x)
const ey = Math.max(this.dragStart.y, this.dragCurrent.y)
const rectW = ex - sx
const rectH = ey - sy
if (rectW < 10 || rectH < 10) {
this._resetDrag()
return
}
if (this.dragCallback) {
this.dragCallback({
length: Math.round(this.dragVideoRect.width),
width: Math.round(this.dragVideoRect.height),
midpointx: Math.round(sx + rectW / 2),
midpointy: Math.round(sy + rectH / 2),
lengthx: Math.round(rectW),
lengthy: Math.round(rectH)
})
}
this._resetDrag()
},
_resetDrag() {
this._unbindDragEvents()
this.dragActive = false
this.dragStart = null
this.dragCurrent = null
this.dragVideoRect = null
this.dragCallback = null
this._removeCanvas()
}
}
}

View File

@ -7,6 +7,7 @@ import {
startScan, stopCruise, startScan, stopCruise,
stopScan, wiper stopScan, wiper
} from '@/api/frontEnd' } from '@/api/frontEnd'
import { dragZoomIn, dragZoomOut } from '@/api/device'
const actions = { const actions = {
setSpeedForScan({ commit }, params) { setSpeedForScan({ commit }, params) {
@ -208,6 +209,26 @@ const actions = {
reject(error) reject(error)
}) })
}) })
},
dragZoomIn({ commit }, params) {
return new Promise((resolve, reject) => {
dragZoomIn(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
dragZoomOut({ commit }, params) {
return new Promise((resolve, reject) => {
dragZoomOut(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
} }
} }

View File

@ -38,8 +38,10 @@ const h265webPlayer = {}
* @see https://github.com/numberwolf/h265web.js/blob/master/example_normal/index.js * @see https://github.com/numberwolf/h265web.js/blob/master/example_normal/index.js
*/ */
const token = 'base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1' const token = 'base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1'
import dragZoom from '../../mixins/dragZoom'
export default { export default {
name: 'H265web', name: 'H265web',
mixins: [dragZoom],
props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'], props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'],
data() { data() {
return { return {
@ -247,6 +249,12 @@ export default {
}, },
setPlaybackRate: function(speed) { setPlaybackRate: function(speed) {
h265webPlayer[this._uid].setPlaybackRate(speed) h265webPlayer[this._uid].setPlaybackRate(speed)
},
getVideoElement() {
return this.$refs.playerBox
},
getVideoRect() {
return this.getVideoElement().getBoundingClientRect()
} }
} }
} }

View File

@ -1,39 +1,41 @@
<template> <template>
<div <div
ref="container" ref="container"
style="width:100%; height: 100%; background-color: #000000;margin:0 auto;position: relative;" style="width:100%; height: 100%; background-color: #000000;margin:0 auto;position: relative;"
@dblclick="fullscreenSwich" @dblclick="fullscreenSwich"
@mouseenter="showBar = true" @mouseleave="showBar = false" @mouseenter="showBar = true" @mouseleave="showBar = false"
> >
<div id="buttonsBox" class="buttons-box" v-if="showButton === undefined || showButton" :style="{ opacity: showBar ? 1 : 0, pointerEvents: showBar ? 'auto' : 'none' }"> <div id="buttonsBox" class="buttons-box" v-if="showButton === undefined || showButton" :style="{ opacity: showBar ? 1 : 0, pointerEvents: showBar ? 'auto' : 'none' }">
<div class="buttons-box-left"> <div class="buttons-box-left">
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" /> <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" />
<i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" /> <i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" />
<i class="iconfont icon-stop jessibuca-btn" @click="stop" /> <i class="iconfont icon-stop jessibuca-btn" @click="stop" />
<i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()" /> <i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()" />
<i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()" /> <i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()" />
</div> </div>
<div class="buttons-box-right"> <div class="buttons-box-right">
<span class="jessibuca-btn">{{ kBps }} kb/s</span> <span class="jessibuca-btn">{{ kBps }} kb/s</span>
<!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>--> <!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
<!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>--> <!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
<i <i
class="iconfont icon-camera1196054easyiconnet jessibuca-btn" class="iconfont icon-camera1196054easyiconnet jessibuca-btn"
style="font-size: 1rem !important" style="font-size: 1rem !important"
@click="screenshot" @click="screenshot"
/> />
<i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick" /> <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick" />
<i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich" /> <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich" />
<i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich" /> <i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich" />
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
const jessibucaPlayer = {} const jessibucaPlayer = {}
import dragZoom from '../../mixins/dragZoom'
export default { export default {
name: 'Jessibuca', name: 'Jessibuca',
mixins: [dragZoom],
props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'], props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'],
data() { data() {
return { return {
@ -280,6 +282,14 @@ export default {
if (jessibucaPlayer[this._uid]) { if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].resize() jessibucaPlayer[this._uid].resize()
} }
},
getVideoElement() {
return this.$refs.container.querySelector('canvas')
},
getVideoRect() {
const container = this.$refs.container
const canvas = this.getVideoElement()
return canvas ? canvas.getBoundingClientRect() : container.getBoundingClientRect()
} }
} }
} }

View File

@ -102,6 +102,16 @@ export default {
if (this.$refs[this.activePlayer]) { if (this.$refs[this.activePlayer]) {
this.$refs[this.activePlayer].pause() this.$refs[this.activePlayer].pause()
} }
},
getVideoRect() {
const player = this.$refs[this.activePlayer]
return player && player.getVideoRect ? player.getVideoRect() : null
},
startDragZoom(callback) {
const player = this.$refs[this.activePlayer]
if (player && player.startDragZoom) {
player.startDragZoom(callback)
}
} }
} }
} }

View File

@ -47,9 +47,12 @@
</div> </div>
</div> </div>
<div class="ptz-func-row"> <div class="ptz-func-row">
<div class="ptz-func-btn" title="拉框放大" @mousedown.prevent="$emit('iris-move', { command: 'in', speed: controSpeed })" @mouseup.prevent="$emit('iris-stop')"> <div class="ptz-func-btn" title="拉框放大" @click="$emit('toggle-drag-zoom')">
<i class="iconfont icon-guangquan" /><span>拉框放大</span> <i class="iconfont icon-guangquan" /><span>拉框放大</span>
</div> </div>
<div class="ptz-func-btn" title="拉框缩小" @click="$emit('toggle-drag-zoom-out')">
<i class="iconfont icon-guangquan-" /><span>拉框缩小</span>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,8 +8,10 @@
<script> <script>
let webrtcPlayer = null let webrtcPlayer = null
import dragZoom from '../../mixins/dragZoom'
export default { export default {
name: 'RtcPlayer', name: 'RtcPlayer',
mixins: [dragZoom],
props: { props: {
videoUrl: { type: String, default: '' }, videoUrl: { type: String, default: '' },
error: { default: '' }, error: { default: '' },
@ -21,7 +23,6 @@ export default {
timer: null timer: null
} }
}, },
mounted() {}, mounted() {},
destroyed() { destroyed() {
clearTimeout(this.timer) clearTimeout(this.timer)
@ -82,6 +83,35 @@ export default {
console.log('player 事件回调') console.log('player 事件回调')
console.log(type) console.log(type)
console.log(message) console.log(message)
},
getVideoElement() {
return document.getElementById('webRtcPlayerBox')
},
getVideoRect() {
const video = this.getVideoElement()
const rect = video.getBoundingClientRect()
if (video.videoWidth && video.videoHeight) {
const natRatio = video.videoWidth / video.videoHeight
const disRatio = rect.width / rect.height
let w, h, x, y
if (natRatio > disRatio) {
w = rect.width
h = w / natRatio
x = 0
y = (rect.height - h) / 2
} else {
h = rect.height
w = h * natRatio
x = (rect.width - w) / 2
y = 0
}
return {
left: rect.left + x, top: rect.top + y,
right: rect.left + x + w, bottom: rect.top + y + h,
width: w, height: h
}
}
return rect
} }
} }
} }
@ -94,6 +124,7 @@ export default {
#rtcPlayer{ #rtcPlayer{
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative;
} }
#webRtcPlayerBox{ #webRtcPlayerBox{
width: 100%; width: 100%;

View File

@ -191,6 +191,8 @@
设备录像控制-停止</el-dropdown-item> 设备录像控制-停止</el-dropdown-item>
<el-dropdown-item command="ptzConfig" :disabled="device == null || device.online === 0"> <el-dropdown-item command="ptzConfig" :disabled="device == null || device.online === 0">
云台配置</el-dropdown-item> 云台配置</el-dropdown-item>
<el-dropdown-item command="audioTalk" :disabled="device == null || device.online === 0">
语音对讲</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
@ -212,12 +214,14 @@
<devicePlayer ref="devicePlayer" /> <devicePlayer ref="devicePlayer" />
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" /> <channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
<ptzConfig v-if="ptzConfigChannelDeviceId" :device-id="ptzConfigDeviceId" :channel-device-id="ptzConfigChannelDeviceId" @close="closePtzConfig" /> <ptzConfig v-if="ptzConfigChannelDeviceId" :device-id="ptzConfigDeviceId" :channel-device-id="ptzConfigChannelDeviceId" @close="closePtzConfig" />
<audioTalk ref="audioTalk" />
</div> </div>
</template> </template>
<script> <script>
import devicePlayer from '../../dialog/devicePlayer.vue' import devicePlayer from '../../dialog/devicePlayer.vue'
import audioTalk from '../../dialog/audioTalk.vue'
import Edit from './edit.vue' import Edit from './edit.vue'
import ptzConfig from '@/views/device/common/ptzConfig.vue' import ptzConfig from '@/views/device/common/ptzConfig.vue'
@ -225,6 +229,7 @@ export default {
name: 'ChannelList', name: 'ChannelList',
components: { components: {
devicePlayer, devicePlayer,
audioTalk,
ChannelEdit: Edit, ChannelEdit: Edit,
ptzConfig ptzConfig
}, },
@ -395,6 +400,8 @@ export default {
console.log(itemData.channelId) console.log(itemData.channelId)
this.ptzConfigDeviceId = this.deviceId this.ptzConfigDeviceId = this.deviceId
this.ptzConfigChannelDeviceId = itemData.deviceId this.ptzConfigChannelDeviceId = itemData.deviceId
} else if (command === 'audioTalk') {
this.$refs.audioTalk.openDialog(this.deviceId, itemData.deviceId)
} }
}, },
queryRecords: function(itemData) { queryRecords: function(itemData) {

View File

@ -0,0 +1,248 @@
<template>
<div>
<el-dialog
title="语音对讲"
top="10vh"
width="65vw"
:close-on-click-modal="false"
:visible.sync="showDialog"
@close="close()"
>
<div style="display: flex; gap: 16px;">
<div style="flex: 1; min-width: 0;">
<div v-if="!showPlayer" class="player-placeholder">
<el-button
type="primary"
icon="el-icon-video-play"
:loading="previewLoading"
@click="startPreview"
>开启预览</el-button>
</div>
<playerTabs
v-if="showPlayer"
ref="playerTabs"
style="min-height: 60vh;"
:has-audio="hasAudio"
:show-button="true"
/>
</div>
<div class="broadcast-panel">
<div style="text-align: center;">
<video id="audioTalkVideo" controls autoplay style="width: 0; height: 0">
Your browser is too old which doesn't support HTML5 video.
</video>
<el-radio-group v-model="talkMode" size="big" @change="onModeChange">
<el-radio-button :label="false">喊话</el-radio-button>
<el-radio-button :label="true">对讲</el-radio-button>
</el-radio-group>
<p style="color: #909399; font-size: 14px; margin-top: 4px;">
{{ talkMode ? '双向语音交互,可听到设备声音' : '单向喊话,仅向设备发送语音' }}
</p>
</div>
<div style="text-align: center;">
<el-button
:type="getTalkButtonType()"
:disabled="talkStatus === -2"
circle
icon="el-icon-microphone"
style="font-size: 32px; padding: 24px;"
@click="talkButtonClick()"
/>
<p style="margin-top: 16px; color: #606266;">
<span v-if="talkStatus === -2">正在释放资源</span>
<span v-if="talkStatus === -1">点击开始{{ talkMode ? '对讲' : '喊话' }}</span>
<span v-if="talkStatus === 0">等待接通中...</span>
<span v-if="talkStatus === 1">请说话</span>
</p>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import playerTabs from '../common/playerTabs.vue'
export default {
name: 'AudioTalk',
components: { playerTabs },
data() {
return {
showDialog: false,
showPlayer: false,
previewLoading: false,
deviceId: null,
channelId: null,
hasAudio: false,
streamInfo: null,
talkMode: false,
talkStatus: -1,
broadcastRtc: null,
talkAudioRtc: null,
talkAudioRetryTimer: null,
talkAudioFailed: false,
talkAudioPlayStream: null,
playConnected: false
}
},
created() {
this.talkStatus = -1
},
methods: {
openDialog(deviceId, channelId) {
if (this.showDialog) return
this.deviceId = deviceId
this.channelId = channelId
this.talkMode = false
this.showPlayer = false
this.streamInfo = null
this.showDialog = true
},
onModeChange() {
if (this.talkStatus > -1) {
this.stopTalk()
}
},
startPreview() {
this.previewLoading = true
this.$store.dispatch('play/play', [this.deviceId, this.channelId])
.then(data => {
this.streamInfo = data
this.hasAudio = data.hasAudio
this.showPlayer = true
this.$nextTick(() => {
if (this.$refs.playerTabs) {
this.$refs.playerTabs.setStreamInfo(data)
}
})
})
.catch(e => {
this.$message({ showClose: true, message: e, type: 'error' })
})
.finally(() => {
this.previewLoading = false
})
},
getTalkButtonType() {
if (this.talkStatus === -2) return 'primary'
if (this.talkStatus === -1) return 'primary'
if (this.talkStatus === 0) return 'warning'
if (this.talkStatus === 1) return 'danger'
},
async talkButtonClick() {
if (this.talkStatus === -1) {
await this.startTalk()
} else if (this.talkStatus === 1) {
this.stopTalk()
}
},
async startTalk() {
this.talkStatus = 0
try {
const data = await this.$store.dispatch('play/broadcastStart', [this.deviceId, this.channelId, this.talkMode])
const si = data.streamInfo
const url = document.location.protocol.includes('https') ? si.rtcs : si.rtc
this.startWebrtcPush(url)
} catch (e) {
this.$message({ showClose: true, message: e, type: 'error' })
this.talkStatus = -1
}
},
startWebrtcPush(url) {
this.$store.dispatch('user/getUserInfo')
.then((data) => {
if (data === null) { this.talkStatus = -1; return }
const pushKey = data.pushKey
url += '&sign=' + pushKey
if (this.broadcastRtc) {
this.broadcastRtc.close()
}
this.broadcastRtc = new ZLMRTCClient.Endpoint({
debug: true,
zlmsdpUrl: url,
simulecast: false,
useCamera: false,
audioEnable: true,
videoEnable: false,
recvOnly: false
})
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, () => { this.talkStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, () => { this.talkStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, () => { this.talkStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => {
if (e === 'connecting') this.talkStatus = 0
else if (e === 'connected') this.talkStatus = 1
else if (e === 'disconnected') this.talkStatus = -1
})
this.broadcastRtc.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, () => { this.talkStatus = -1 })
})
.catch(() => { this.talkStatus = -1 })
},
async stopTalk() {
this.talkStatus = -2
if (this.broadcastRtc) {
this.broadcastRtc.close()
this.broadcastRtc = null
}
if (this.talkAudioRtc) {
this.talkAudioRtc.close()
this.talkAudioRtc = null
}
if (this.talkAudioRetryTimer) {
clearTimeout(this.talkAudioRetryTimer)
this.talkAudioRetryTimer = null
}
this.talkAudioFailed = false
this.talkAudioPlayStream = null
this.playConnected = false
try {
await this.$store.dispatch('play/broadcastStop', [this.deviceId, this.channelId])
} catch (e) {
console.warn('停止对讲失败', e)
}
this.talkStatus = -1
},
close() {
if (this.showPlayer && this.$refs.playerTabs) {
this.$refs.playerTabs.stop()
}
this.stopTalk()
this.streamInfo = null
this.showPlayer = false
this.showDialog = false
}
}
}
</script>
<style scoped>
.player-placeholder {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 16 / 9;
background: #1a1a1a;
}
.broadcast-panel {
width: 220px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 10px;
border-left: 1px solid #ebeef5;
}
.broadcast-panel > div:first-child {
flex-shrink: 0;
}
.broadcast-panel > div:last-child {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>

View File

@ -5,7 +5,7 @@
v-if="showVideoDialog" v-if="showVideoDialog"
v-el-drag-dialog v-el-drag-dialog
title="视频播放" title="视频播放"
top="2vh" top="10vh"
width="65vw" width="65vw"
:close-on-click-modal="false" :close-on-click-modal="false"
:visible.sync="showVideoDialog" :visible.sync="showVideoDialog"
@ -15,7 +15,8 @@
<div class="player-side"> <div class="player-side">
<div class="player-container" :style="{ height: playerHeight }"> <div class="player-container" :style="{ height: playerHeight }">
<playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true" @playerChanged="playerChanged"/> <playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true"
@playerChanged="playerChanged" />
</div> </div>
</div> </div>
@ -29,6 +30,8 @@
@focus-stop="onFocusStop" @focus-stop="onFocusStop"
@iris-move="onIrisMove" @iris-move="onIrisMove"
@iris-stop="onIrisStop" @iris-stop="onIrisStop"
@toggle-drag-zoom="toggleDragZoom('in')"
@toggle-drag-zoom-out="toggleDragZoom('out')"
/> />
</div> </div>
@ -97,23 +100,6 @@
<el-tab-pane label="编码信息" name="codec"> <el-tab-pane label="编码信息" name="codec">
<mediaInfo v-if="tabActiveName === 'codec'" ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" /> <mediaInfo v-if="tabActiveName === 'codec'" ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="showBroadcast" label="语音对讲" name="broadcast">
<div style="padding: 0 10px">
<el-radio-group v-model="broadcastMode" :disabled="broadcastStatus !== -1">
<el-radio :label="true">喊话(Broadcast)</el-radio>
<el-radio :label="false">对讲(Talk)</el-radio>
</el-radio-group>
</div>
<div class="trank" style="text-align: center;">
<el-button :type="getBroadcastStatus()" :disabled="broadcastStatus === -2" circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;" @click="broadcastStatusClick()" />
<p>
<span v-if="broadcastStatus === -2">正在释放资源</span>
<span v-if="broadcastStatus === -1">点击开始对讲</span>
<span v-if="broadcastStatus === 0">等待接通中...</span>
<span v-if="broadcastStatus === 1">请说话</span>
</p>
</div>
</el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -124,7 +110,6 @@
<script> <script>
import elDragDialog from '@/directive/el-drag-dialog' import elDragDialog from '@/directive/el-drag-dialog'
import crypto from 'crypto'
import playerTabs from '../common/playerTabs.vue' import playerTabs from '../common/playerTabs.vue'
import ptzControls from '../common/ptzControls.vue' import ptzControls from '../common/ptzControls.vue'
import PtzPreset from '../common/ptzPreset.vue' import PtzPreset from '../common/ptzPreset.vue'
@ -147,16 +132,13 @@ export default {
hasAudio: false, hasAudio: false,
isLoging: false, isLoging: false,
showVideoDialog: false, showVideoDialog: false,
showBroadcast: true,
streamInfo: null, streamInfo: null,
broadcastMode: true,
broadcastRtc: null,
broadcastStatus: -1,
playerHeight: '48vh', playerHeight: '48vh',
playerUrlInfo: { playerUrlInfo: {
playerUrl: null, playerUrl: null,
playUrl: null, playUrl: null,
} },
dragZoomDirection: ''
} }
}, },
computed: { computed: {
@ -165,7 +147,6 @@ export default {
} }
}, },
created() { created() {
this.broadcastStatus = -1
}, },
methods: { methods: {
ptzSpeed(speed) { ptzSpeed(speed) {
@ -249,68 +230,34 @@ export default {
} }
this.videoUrl = '' this.videoUrl = ''
this.showVideoDialog = false this.showVideoDialog = false
this.stopBroadcast()
}, },
copyUrl: function(dropdownItem) { copyUrl: function(dropdownItem) {
this.$copyText(dropdownItem).then(() => { this.$copyText(dropdownItem).then(() => {
this.$message.success({ showClose: true, message: '成功拷贝到粘贴板' }) this.$message.success({ showClose: true, message: '成功拷贝到粘贴板' })
}, () => {}) }, () => {})
}, },
getBroadcastStatus() {
if (this.broadcastStatus == -2) return 'primary' toggleDragZoom(direction) {
if (this.broadcastStatus == -1) return 'primary' this.dragZoomDirection = direction
if (this.broadcastStatus == 0) return 'warning' this.$refs.playerTabs.startDragZoom((params) => {
if (this.broadcastStatus === 1) return 'danger' params.deviceId = this.deviceId
params.channelId = this.channelId
const action = this.dragZoomDirection === 'in' ? 'frontEnd/dragZoomIn' : 'frontEnd/dragZoomOut'
const successMsg = this.dragZoomDirection === 'in' ? '拉框放大成功' : '拉框缩小成功'
const failMsg = this.dragZoomDirection === 'in' ? '拉框放大失败' : '拉框缩小失败'
this.$store.dispatch(action, params).then(() => {
this.$message({ showClose: true, message: successMsg, type: 'success' })
}).catch(() => {
this.$message({ showClose: true, message: failMsg, type: 'error' })
})
this.dragZoomDirection = ''
})
}, },
broadcastStatusClick() {
if (this.broadcastStatus === -1) {
this.broadcastStatus = 0
this.$store.dispatch('play/broadcastStart', [this.deviceId, this.channelId, this.broadcastMode])
.then(data => {
const si = data.streamInfo
if (document.location.protocol.includes('https')) {
this.startBroadcast(si.rtcs)
} else {
this.startBroadcast(si.rtc)
}
})
} else if (this.broadcastStatus === 1) {
this.broadcastStatus = -1
this.broadcastRtc.close()
}
},
startBroadcast(url) {
this.$store.dispatch('user/getUserInfo')
.then((data) => {
if (data === null) { this.broadcastStatus = -1; return }
const pushKey = data.pushKey
url += '&sign=' + crypto.createHash('md5').update(pushKey, 'utf8').digest('hex')
this.broadcastRtc = new ZLMRTCClient.Endpoint({
debug: true, zlmsdpUrl: url, simulecast: false, useCamera: false,
audioEnable: true, videoEnable: false, recvOnly: false
})
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, () => { this.broadcastStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, () => { this.broadcastStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, () => { this.broadcastStatus = -1 })
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => {
if (e === 'connecting') this.broadcastStatus = 0
else if (e === 'connected') this.broadcastStatus = 1
else if (e === 'disconnected') this.broadcastStatus = -1
})
this.broadcastRtc.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, () => { this.broadcastStatus = -1 })
}).catch(() => { this.broadcastStatus = -1 })
},
stopBroadcast() {
this.broadcastRtc && this.broadcastRtc.close()
this.broadcastStatus = -1
this.$store.dispatch('play/broadcastStop', [this.deviceId, this.channelId])
}
} }
} }
</script> </script>
<style> <style>
#devicePlayer .el-dialog { margin-top: 2vh !important; }
#devicePlayer .el-dialog__body { padding: 10px 20px; } #devicePlayer .el-dialog__body { padding: 10px 20px; }
.dhsdk-player-body { display: flex; gap: 16px; } .dhsdk-player-body { display: flex; gap: 16px; }
.player-side { flex: 3; min-width: 0; } .player-side { flex: 3; min-width: 0; }