mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-06-22 02:57:49 +08:00
支持拉框放大和拉框缩小
This commit is contained in:
parent
3417244705
commit
70bc01bd90
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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
204
web/src/mixins/dragZoom.js
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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%;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
248
web/src/views/dialog/audioTalk.vue
Normal file
248
web/src/views/dialog/audioTalk.vue
Normal 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>
|
||||||
@ -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; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user