mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-06-30 22:17:48 +08:00
Compare commits
1 Commits
ac761b9a7d
...
68ae65ac94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68ae65ac94 |
@ -16,7 +16,6 @@ public class ChannelDataType {
|
|||||||
public final static String DOWNLOAD_SERVICE = "sourceChannelDownloadService";
|
public final static String DOWNLOAD_SERVICE = "sourceChannelDownloadService";
|
||||||
public final static String PTZ_SERVICE = "sourceChannelPTZService";
|
public final static String PTZ_SERVICE = "sourceChannelPTZService";
|
||||||
public final static String OTHER_SERVICE = "sourceChannelOtherService";
|
public final static String OTHER_SERVICE = "sourceChannelOtherService";
|
||||||
public final static String BROADCAST_SERVICE = "sourceChannelBroadcastService";
|
|
||||||
|
|
||||||
|
|
||||||
public static String getDateTypeDesc(Integer dataType) {
|
public static String getDateTypeDesc(Integer dataType) {
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.bean;
|
|
||||||
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
public class FrontEndControlCodeForDragZoom implements IFrontEndControlCode {
|
|
||||||
|
|
||||||
private final FrontEndControlType type = FrontEndControlType.DRAG_ZOOM;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FrontEndControlType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 辅助开关控制指令: 1为zoomIn 拉框放大, 2为zoomOut 拉框缩小
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private Integer code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 播放窗口长度像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer length;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 播放窗口长度像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉框中心的横轴坐标像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer midPointX;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉框中心的纵轴坐标像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer midPointY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉框长度像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer lengthX;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉框宽度像素值(必选)
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
protected Integer lengthY;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encode() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,5 +2,5 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
|||||||
|
|
||||||
public enum FrontEndControlType {
|
public enum FrontEndControlType {
|
||||||
|
|
||||||
PTZ,FI,PRESET,TOUR,SCAN,AUXILIARY,DRAG_ZOOM
|
PTZ,FI,PRESET,TOUR,SCAN,AUXILIARY
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import com.genersoft.iot.vmp.gb28181.utils.VectorTileCatch;
|
|||||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioTalkResult;
|
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||||
@ -353,42 +352,6 @@ public class ChannelController {
|
|||||||
channelPlayService.stopPlay(channel);
|
channelPlayService.stopPlay(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "开始对讲", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@GetMapping("/talk/start")
|
|
||||||
public AudioTalkResult startTalk(Integer channelId){
|
|
||||||
Assert.notNull(channelId,"参数异常");
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
return channelPlayService.startTalk(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "停止对讲", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@GetMapping("/talk/stop")
|
|
||||||
public void stopTalk(Integer channelId){
|
|
||||||
Assert.notNull(channelId,"参数异常");
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
channelPlayService.stopTalk(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "开始喊话", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@GetMapping("/broadcast/start")
|
|
||||||
public AudioTalkResult startBroadcast(Integer channelId){
|
|
||||||
Assert.notNull(channelId,"参数异常");
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
return channelPlayService.startBroadcast(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "停止喊话", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@GetMapping("/broadcast/stop")
|
|
||||||
public void stopBroadcast(Integer channelId){
|
|
||||||
Assert.notNull(channelId,"参数异常");
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
channelPlayService.stopBroadcast(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
@Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||||
@Parameter(name = "channelId", description = "通道ID", required = true)
|
@Parameter(name = "channelId", description = "通道ID", required = true)
|
||||||
@Parameter(name = "startTime", description = "开始时间", required = true)
|
@Parameter(name = "startTime", description = "开始时间", required = true)
|
||||||
|
|||||||
@ -598,94 +598,4 @@ public class ChannelFrontEndController {
|
|||||||
channelControlService.auxiliary(channel, controlCode, callback);
|
channelControlService.auxiliary(channel, controlCode, callback);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "拉框放大", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@Parameter(name = "channelId", description = "通道ID", required = true)
|
|
||||||
@Parameter(name = "length", description = "播放窗口长度像素值", required = true)
|
|
||||||
@Parameter(name = "width", description = "播放窗口宽度像素值", required = true)
|
|
||||||
@Parameter(name = "midPointX", description = "拉框中心的横轴坐标像素值", required = true)
|
|
||||||
@Parameter(name = "midPointY", description = "拉框中心的纵轴坐标像素值", required = true)
|
|
||||||
@Parameter(name = "lengthX", description = "拉框长度像素值", required = true)
|
|
||||||
@Parameter(name = "lengthY", description = "拉框宽度像素值", required = true)
|
|
||||||
@GetMapping("/drag_zoom_in")
|
|
||||||
public DeferredResult<WVPResult<String>> dragZoomIn(Integer channelId, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY){
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("[通用通道]拉框放大 API调用,channelId:{} ,length:{} ,width:{} ,midPointX:{} ,midPointY:{} ,lengthX:{} ,lengthY:{}",channelId, length, width, midPointX, midPointY, lengthX, lengthY);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
|
|
||||||
FrontEndControlCodeForDragZoom controlCode = new FrontEndControlCodeForDragZoom();
|
|
||||||
controlCode.setCode(1);
|
|
||||||
controlCode.setLength(length);
|
|
||||||
controlCode.setWidth(width);
|
|
||||||
controlCode.setMidPointX(midPointX);
|
|
||||||
controlCode.setMidPointY(midPointY);
|
|
||||||
controlCode.setLengthX(lengthX);
|
|
||||||
controlCode.setLengthY(lengthY);
|
|
||||||
|
|
||||||
DeferredResult<WVPResult<String>> result = new DeferredResult<>();
|
|
||||||
|
|
||||||
result.onTimeout(()->{
|
|
||||||
WVPResult<String> wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时");
|
|
||||||
result.setResult(wvpResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
ErrorCallback<String> callback = (code, msg, data) -> {
|
|
||||||
WVPResult<String> wvpResult = new WVPResult<>();
|
|
||||||
wvpResult.setCode(code);
|
|
||||||
wvpResult.setMsg(msg);
|
|
||||||
wvpResult.setData(data);
|
|
||||||
result.setResult(wvpResult);
|
|
||||||
};
|
|
||||||
channelControlService.dragZoom(channel, controlCode, callback);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "拉框缩小", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
|
||||||
@Parameter(name = "channelId", description = "通道ID", required = true)
|
|
||||||
@Parameter(name = "length", description = "播放窗口长度像素值", required = true)
|
|
||||||
@Parameter(name = "width", description = "播放窗口宽度像素值", required = true)
|
|
||||||
@Parameter(name = "midPointX", description = "拉框中心的横轴坐标像素值", required = true)
|
|
||||||
@Parameter(name = "midPointY", description = "拉框中心的纵轴坐标像素值", required = true)
|
|
||||||
@Parameter(name = "lengthX", description = "拉框长度像素值", required = true)
|
|
||||||
@Parameter(name = "lengthY", description = "拉框宽度像素值", required = true)
|
|
||||||
@GetMapping("/drag_zoom_out")
|
|
||||||
public DeferredResult<WVPResult<String>> dragZoomOut(Integer channelId, Integer length, Integer width, Integer midPointX, Integer midPointY, Integer lengthX, Integer lengthY){
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("[通用通道]拉框缩小 API调用,channelId:{} ,length:{} ,width:{} ,midPointX:{} ,midPointY:{} ,lengthX:{} ,lengthY:{}",channelId, length, width, midPointX, midPointY, lengthX, lengthY);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommonGBChannel channel = channelService.getOne(channelId);
|
|
||||||
Assert.notNull(channel, "通道不存在");
|
|
||||||
|
|
||||||
FrontEndControlCodeForDragZoom controlCode = new FrontEndControlCodeForDragZoom();
|
|
||||||
controlCode.setCode(2);
|
|
||||||
controlCode.setLength(length);
|
|
||||||
controlCode.setWidth(width);
|
|
||||||
controlCode.setMidPointX(midPointX);
|
|
||||||
controlCode.setMidPointY(midPointY);
|
|
||||||
controlCode.setLengthX(lengthX);
|
|
||||||
controlCode.setLengthY(lengthY);
|
|
||||||
|
|
||||||
DeferredResult<WVPResult<String>> result = new DeferredResult<>();
|
|
||||||
|
|
||||||
result.onTimeout(()->{
|
|
||||||
WVPResult<String> wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时");
|
|
||||||
result.setResult(wvpResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
ErrorCallback<String> callback = (code, msg, data) -> {
|
|
||||||
WVPResult<String> wvpResult = new WVPResult<>();
|
|
||||||
wvpResult.setCode(code);
|
|
||||||
wvpResult.setMsg(msg);
|
|
||||||
wvpResult.setData(data);
|
|
||||||
result.setResult(wvpResult);
|
|
||||||
};
|
|
||||||
channelControlService.dragZoom(channel, controlCode, callback);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -165,50 +165,66 @@ public class DeviceControl {
|
|||||||
@Parameter(name = "channelId", description = "通道国标编号", required = true)
|
@Parameter(name = "channelId", description = "通道国标编号", required = true)
|
||||||
@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_in")
|
@GetMapping("drag_zoom/zoom_in")
|
||||||
public void dragZoomIn(@RequestParam String deviceId, String channelId,
|
public DeferredResult<WVPResult<String>> dragZoomIn(@RequestParam String deviceId, String channelId,
|
||||||
@RequestParam int length,
|
@RequestParam int length,
|
||||||
@RequestParam int width,
|
@RequestParam int width,
|
||||||
@RequestParam int midPointX,
|
@RequestParam int midpointx,
|
||||||
@RequestParam int midPointY,
|
@RequestParam int midpointy,
|
||||||
@RequestParam int lengthX,
|
@RequestParam int lengthx,
|
||||||
@RequestParam int lengthY) {
|
@RequestParam int lengthy) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug(String.format("设备拉框放大 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midPointX:%d ,midPointY:%d ,lengthX:%d ,lengthY:%d",deviceId, channelId, length, width, midPointX, midPointY,lengthX, lengthY));
|
log.debug(String.format("设备拉框放大 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midpointx:%d ,midpointy:%d ,lengthx:%d ,lengthy:%d",deviceId, channelId, length, width, midpointx, midpointy,lengthx, lengthy));
|
||||||
}
|
}
|
||||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||||
Assert.notNull(device, "设备不存在");
|
Assert.notNull(device, "设备不存在");
|
||||||
deviceService.dragZoomIn(device, channelId, length, width, midPointX, midPointY, lengthX, lengthY);
|
DeferredResult<WVPResult<String>> result = new DeferredResult<>();
|
||||||
|
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 void dragZoomOut(@RequestParam String deviceId,
|
public DeferredResult<WVPResult<String>> 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,
|
||||||
@RequestParam int midPointX,
|
@RequestParam int midpointx,
|
||||||
@RequestParam int midPointY,
|
@RequestParam int midpointy,
|
||||||
@RequestParam int lengthX,
|
@RequestParam int lengthx,
|
||||||
@RequestParam int lengthY){
|
@RequestParam int lengthy){
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug(String.format("设备拉框缩小 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midPointX:%d ,midPointY:%d ,lengthX:%d ,lengthY:%d",deviceId, channelId, length, width, midPointX, midPointY,lengthX, lengthY));
|
log.debug(String.format("设备拉框缩小 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midpointx:%d ,midpointy:%d ,lengthx:%d ,lengthy:%d",deviceId, channelId, length, width, midpointx, midpointy,lengthx, lengthy));
|
||||||
}
|
}
|
||||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||||
Assert.notNull(device, "设备不存在");
|
Assert.notNull(device, "设备不存在");
|
||||||
deviceService.dragZoomOut(device, channelId, length, width, midPointX, midPointY, lengthX,lengthY);
|
DeferredResult<WVPResult<String>> result = new DeferredResult<>();
|
||||||
|
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);
|
void dragZoomIn(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 dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback);
|
||||||
|
|
||||||
void deviceStatus(Device device, ErrorCallback<String> callback);
|
void deviceStatus(Device device, ErrorCallback<String> callback);
|
||||||
|
|
||||||
|
|||||||
@ -16,5 +16,4 @@ public interface IGbChannelControlService {
|
|||||||
void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper controlCode, ErrorCallback<String> callback);
|
void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper controlCode, ErrorCallback<String> callback);
|
||||||
void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback<String> callback);
|
void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback<String> callback);
|
||||||
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
||||||
void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo;
|
|||||||
import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo;
|
import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioTalkResult;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -42,12 +41,4 @@ public interface IGbChannelPlayService {
|
|||||||
|
|
||||||
|
|
||||||
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback);
|
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback);
|
||||||
|
|
||||||
AudioTalkResult startTalk(CommonGBChannel channel);
|
|
||||||
|
|
||||||
void stopTalk(CommonGBChannel channel);
|
|
||||||
|
|
||||||
AudioTalkResult startBroadcast(CommonGBChannel channel);
|
|
||||||
|
|
||||||
void stopBroadcast(CommonGBChannel channel);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,8 +18,4 @@ public interface IPTZService {
|
|||||||
|
|
||||||
void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
||||||
|
|
||||||
void dragZoomIn(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY);
|
|
||||||
|
|
||||||
void dragZoomOut(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.service;
|
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
|
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioTalkResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 资源能力接入-语音对讲
|
|
||||||
*/
|
|
||||||
public interface ISourceBroadcastService {
|
|
||||||
|
|
||||||
AudioTalkResult startTalk(CommonGBChannel channel);
|
|
||||||
|
|
||||||
void stopTalk(CommonGBChannel channel);
|
|
||||||
|
|
||||||
AudioTalkResult startBroadcast(CommonGBChannel channel);
|
|
||||||
|
|
||||||
void stopBroadcast(CommonGBChannel channel);
|
|
||||||
}
|
|
||||||
@ -25,6 +25,4 @@ public interface ISourcePTZService {
|
|||||||
void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback<String> callback);
|
void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback<String> callback);
|
||||||
|
|
||||||
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
|
||||||
|
|
||||||
void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -574,9 +574,9 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback<Boolean> callback) {
|
public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback<Boolean> callback) {
|
||||||
|
log.info("[移除目录订阅]: {}", device.getDeviceId());
|
||||||
String key = SubscribeTaskForCatalog.getKey(device);
|
String key = SubscribeTaskForCatalog.getKey(device);
|
||||||
if (subscribeTaskRunner.containsKey(key)) {
|
if (subscribeTaskRunner.containsKey(key)) {
|
||||||
log.info("[移除目录订阅]: {}", device.getDeviceId());
|
|
||||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||||
if (transactionInfo == null) {
|
if (transactionInfo == null) {
|
||||||
log.warn("[移除目录订阅] 未找到事务信息,{}", device.getDeviceId());
|
log.warn("[移除目录订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||||
@ -638,9 +638,9 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback) {
|
public boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback) {
|
||||||
|
log.info("[移除移动位置订阅]: {}", device.getDeviceId());
|
||||||
String key = SubscribeTaskForMobilPosition.getKey(device);
|
String key = SubscribeTaskForMobilPosition.getKey(device);
|
||||||
if (subscribeTaskRunner.containsKey(key)) {
|
if (subscribeTaskRunner.containsKey(key)) {
|
||||||
log.info("[移除移动位置订阅]: {}", device.getDeviceId());
|
|
||||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||||
if (transactionInfo == null) {
|
if (transactionInfo == null) {
|
||||||
log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId());
|
log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||||
@ -703,9 +703,9 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeAlarmSubscribe(Device device, CommonCallback<Boolean> callback) {
|
public boolean removeAlarmSubscribe(Device device, CommonCallback<Boolean> callback) {
|
||||||
|
log.info("[移除报警订阅]: {}", device.getDeviceId());
|
||||||
String key = SubscribeTaskForAlarm.getKey(device);
|
String key = SubscribeTaskForAlarm.getKey(device);
|
||||||
if (subscribeTaskRunner.containsKey(key)) {
|
if (subscribeTaskRunner.containsKey(key)) {
|
||||||
log.info("[移除报警订阅]: {}", device.getDeviceId());
|
|
||||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||||
if (transactionInfo == null) {
|
if (transactionInfo == null) {
|
||||||
log.warn("[移除报警订阅] 未找到事务信息,{}", device.getDeviceId());
|
log.warn("[移除报警订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||||
@ -1276,9 +1276,9 @@ 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) {
|
public void dragZoomIn(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1286,23 +1286,24 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
cmdXml.append("<DragZoomIn>\r\n");
|
cmdXml.append("<DragZoomIn>\r\n");
|
||||||
cmdXml.append("<Length>" + length+ "</Length>\r\n");
|
cmdXml.append("<Length>" + length+ "</Length>\r\n");
|
||||||
cmdXml.append("<Width>" + width+ "</Width>\r\n");
|
cmdXml.append("<Width>" + width+ "</Width>\r\n");
|
||||||
cmdXml.append("<MidPointX>" + midPointX+ "</MidPointX>\r\n");
|
cmdXml.append("<MidPointX>" + midpointx+ "</MidPointX>\r\n");
|
||||||
cmdXml.append("<MidPointY>" + midPointY+ "</MidPointY>\r\n");
|
cmdXml.append("<MidPointY>" + midpointy+ "</MidPointY>\r\n");
|
||||||
cmdXml.append("<LengthX>" + lengthX+ "</LengthX>\r\n");
|
cmdXml.append("<LengthX>" + lengthx+ "</LengthX>\r\n");
|
||||||
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());
|
sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback);
|
||||||
} 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) {
|
public void dragZoomOut(Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy, ErrorCallback<String> callback) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1310,15 +1311,16 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
cmdXml.append("<DragZoomOut>\r\n");
|
cmdXml.append("<DragZoomOut>\r\n");
|
||||||
cmdXml.append("<Length>" + length+ "</Length>\r\n");
|
cmdXml.append("<Length>" + length+ "</Length>\r\n");
|
||||||
cmdXml.append("<Width>" + width+ "</Width>\r\n");
|
cmdXml.append("<Width>" + width+ "</Width>\r\n");
|
||||||
cmdXml.append("<MidPointX>" + midPointX+ "</MidPointX>\r\n");
|
cmdXml.append("<MidPointX>" + midpointx+ "</MidPointX>\r\n");
|
||||||
cmdXml.append("<MidPointY>" + midPointY+ "</MidPointY>\r\n");
|
cmdXml.append("<MidPointY>" + midpointy+ "</MidPointY>\r\n");
|
||||||
cmdXml.append("<LengthX>" + lengthX+ "</LengthX>\r\n");
|
cmdXml.append("<LengthX>" + lengthx+ "</LengthX>\r\n");
|
||||||
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());
|
sipCommander.dragZoomCmd(device, channelId, cmdXml.toString(), callback);
|
||||||
} 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,17 +125,4 @@ public class GbChannelControlServiceImpl implements IGbChannelControlService {
|
|||||||
}
|
}
|
||||||
sourcePTZService.queryPreset(channel, callback);
|
sourcePTZService.queryPreset(channel, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback) {
|
|
||||||
log.info("[通用通道] 拉框{} 类型: {}, 编号:{}", frontEndControlCode.getCode() == 1 ? "放大": "缩小", channel.getDataType(), channel.getGbDeviceId());
|
|
||||||
Integer dataType = channel.getDataType();
|
|
||||||
ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType);
|
|
||||||
if (sourcePTZService == null) {
|
|
||||||
// 通道数据异常
|
|
||||||
log.error("[点播通用通道] 类型: {} 不支持拉框控制", dataType);
|
|
||||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
|
||||||
}
|
|
||||||
sourcePTZService.dragZoom(channel, frontEndControlCode, callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,9 @@ import com.genersoft.iot.vmp.conf.UserSetting;
|
|||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper;
|
import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService;
|
import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.ISourceBroadcastService;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService;
|
import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService;
|
import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService;
|
||||||
import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService;
|
import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioTalkResult;
|
|
||||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -41,9 +39,6 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private Map<String, ISourceDownloadService> sourceDownloadServiceMap;
|
private Map<String, ISourceDownloadService> sourceDownloadServiceMap;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Map<String, ISourceBroadcastService> sourceBroadcastServiceMap;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startInvite(CommonGBChannel channel, InviteMessageInfo inviteInfo, Platform platform, ErrorCallback<StreamInfo> callback) {
|
public void startInvite(CommonGBChannel channel, InviteMessageInfo inviteInfo, Platform platform, ErrorCallback<StreamInfo> callback) {
|
||||||
@ -252,52 +247,4 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
|
|||||||
}
|
}
|
||||||
sourceChannelPlayService.getSnap(channel, callback);
|
sourceChannelPlayService.getSnap(channel, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudioTalkResult startTalk(CommonGBChannel channel) {
|
|
||||||
log.info("[通用通道] 开始对讲, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId());
|
|
||||||
Integer dataType = channel.getDataType();
|
|
||||||
ISourceBroadcastService broadcastService = sourceBroadcastServiceMap.get(ChannelDataType.BROADCAST_SERVICE + dataType);
|
|
||||||
if (broadcastService == null) {
|
|
||||||
log.error("[通用通道] 类型编号: {} 不支持对讲", dataType);
|
|
||||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
|
||||||
}
|
|
||||||
return broadcastService.startTalk(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopTalk(CommonGBChannel channel) {
|
|
||||||
log.info("[通用通道] 停止对讲, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId());
|
|
||||||
Integer dataType = channel.getDataType();
|
|
||||||
ISourceBroadcastService broadcastService = sourceBroadcastServiceMap.get(ChannelDataType.BROADCAST_SERVICE + dataType);
|
|
||||||
if (broadcastService == null) {
|
|
||||||
log.error("[通用通道] 类型编号: {} 不支持对讲", dataType);
|
|
||||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
|
||||||
}
|
|
||||||
broadcastService.stopTalk(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudioTalkResult startBroadcast(CommonGBChannel channel) {
|
|
||||||
log.info("[通用通道] 开始喊话, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId());
|
|
||||||
Integer dataType = channel.getDataType();
|
|
||||||
ISourceBroadcastService broadcastService = sourceBroadcastServiceMap.get(ChannelDataType.BROADCAST_SERVICE + dataType);
|
|
||||||
if (broadcastService == null) {
|
|
||||||
log.error("[通用通道] 类型编号: {} 不支持喊话", dataType);
|
|
||||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
|
||||||
}
|
|
||||||
return broadcastService.startBroadcast(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopBroadcast(CommonGBChannel channel) {
|
|
||||||
log.info("[通用通道] 停止喊话, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId());
|
|
||||||
Integer dataType = channel.getDataType();
|
|
||||||
ISourceBroadcastService broadcastService = sourceBroadcastServiceMap.get(ChannelDataType.BROADCAST_SERVICE + dataType);
|
|
||||||
if (broadcastService == null) {
|
|
||||||
log.error("[通用通道] 类型编号: {} 不支持喊话", dataType);
|
|
||||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
|
||||||
}
|
|
||||||
broadcastService.stopBroadcast(channel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,38 +87,10 @@ public class PTZServiceImpl implements IPTZService {
|
|||||||
if (device == null) {
|
if (device == null) {
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备ID");
|
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备ID");
|
||||||
}
|
}
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
DeviceChannel deviceChannel = deviceChannelService.getOneById(channel.getGbId());
|
||||||
frontEndCommand(device, deviceChannel.getDeviceId(), cmdCode, parameter1, parameter2, combindCode2);
|
frontEndCommand(device, deviceChannel.getDeviceId(), cmdCode, parameter1, parameter2, combindCode2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dragZoomIn(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY) {
|
|
||||||
if (channel.getDataType() != ChannelDataType.GB28181) {
|
|
||||||
log.warn("[INFO 消息] 只有国标通道的支持云台控制, 通道ID: {}", channel.getGbId());
|
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持");
|
|
||||||
}
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备ID");
|
|
||||||
}
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
deviceService.dragZoomIn(device, deviceChannel.getDeviceId(), length, width, midPointX, midPointY, lengthX, lengthY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dragZoomOut(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY) {
|
|
||||||
if (channel.getDataType() != ChannelDataType.GB28181) {
|
|
||||||
log.warn("[INFO 消息] 只有国标通道的支持云台控制, 通道ID: {}", channel.getGbId());
|
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持");
|
|
||||||
}
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备ID");
|
|
||||||
}
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
deviceService.dragZoomOut(device, deviceChannel.getDeviceId(), length, width, midPointX, midPointY, lengthX, lengthY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
public void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
||||||
if (channel.getDataType() != ChannelDataType.GB28181) {
|
if (channel.getDataType() != ChannelDataType.GB28181) {
|
||||||
@ -136,6 +108,4 @@ public class PTZServiceImpl implements IPTZService {
|
|||||||
}
|
}
|
||||||
deviceService.queryPreset(device, deviceChannel.getDeviceId(), callback);
|
deviceService.queryPreset(device, deviceChannel.getDeviceId(), callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1229,12 +1229,6 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
audioBroadcastResult.setApp(app);
|
audioBroadcastResult.setApp(app);
|
||||||
audioBroadcastResult.setStream(stream);
|
audioBroadcastResult.setStream(stream);
|
||||||
audioBroadcastResult.setStreamInfo(new StreamContent(mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
|
audioBroadcastResult.setStreamInfo(new StreamContent(mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
|
||||||
if (!broadcastMode) {
|
|
||||||
audioBroadcastResult.setPlayStreamInfo(new StreamContent(
|
|
||||||
mediaServerService.getStreamInfoByAppAndStream(mediaServerItem,
|
|
||||||
MediaStreamUtil.GB28181_TALK, stream + "_talk",
|
|
||||||
null, null, null, true)));
|
|
||||||
}
|
|
||||||
audioBroadcastResult.setCodec("G.711");
|
audioBroadcastResult.setCodec("G.711");
|
||||||
return audioBroadcastResult;
|
return audioBroadcastResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.service.impl;
|
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
|
|
||||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.service.IPlayService;
|
|
||||||
import com.genersoft.iot.vmp.gb28181.service.ISourceBroadcastService;
|
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
|
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.AudioTalkResult;
|
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Service(ChannelDataType.BROADCAST_SERVICE + ChannelDataType.GB28181)
|
|
||||||
public class SourceBroadcastServiceForGbImpl implements ISourceBroadcastService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IPlayService playService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IDeviceService deviceService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IDeviceChannelService deviceChannelService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudioTalkResult startBroadcast(CommonGBChannel channel) {
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备");
|
|
||||||
}
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
if (deviceChannel == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道");
|
|
||||||
}
|
|
||||||
AudioBroadcastResult abResult = playService.audioBroadcast(
|
|
||||||
device.getDeviceId(), deviceChannel.getDeviceId(), true);
|
|
||||||
AudioTalkResult result = new AudioTalkResult();
|
|
||||||
result.setPushStream(abResult.getStreamInfo());
|
|
||||||
result.setPlayStream(null);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopBroadcast(CommonGBChannel channel) {
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) return;
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
if (deviceChannel == null) return;
|
|
||||||
playService.stopAudioBroadcast(device, deviceChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudioTalkResult startTalk(CommonGBChannel channel) {
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备");
|
|
||||||
}
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
if (deviceChannel == null) {
|
|
||||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道");
|
|
||||||
}
|
|
||||||
AudioBroadcastResult abResult = playService.audioBroadcast(
|
|
||||||
device.getDeviceId(), deviceChannel.getDeviceId(), false);
|
|
||||||
AudioTalkResult result = new AudioTalkResult();
|
|
||||||
result.setPushStream(abResult.getStreamInfo());
|
|
||||||
result.setPlayStream(abResult.getPlayStreamInfo());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopTalk(CommonGBChannel channel) {
|
|
||||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
|
||||||
if (device == null) return;
|
|
||||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
|
||||||
if (deviceChannel == null) return;
|
|
||||||
playService.stopTalk(device, deviceChannel, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -121,11 +121,6 @@ public class SourcePTZServiceForGbImpl implements ISourcePTZService {
|
|||||||
log.error("[FI失败] 未知的聚焦指令 {}", frontEndControlCode.getFocus());
|
log.error("[FI失败] 未知的聚焦指令 {}", frontEndControlCode.getFocus());
|
||||||
callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null);
|
callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null);
|
||||||
}
|
}
|
||||||
if (frontEndControlCode.getFocusSpeed() == null) {
|
|
||||||
callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
focusSpeed = frontEndControlCode.getFocusSpeed();
|
|
||||||
}
|
}
|
||||||
if (frontEndControlCode.getIris() != null) {
|
if (frontEndControlCode.getIris() != null) {
|
||||||
if (frontEndControlCode.getIris() == 0) {
|
if (frontEndControlCode.getIris() == 0) {
|
||||||
@ -136,12 +131,17 @@ public class SourcePTZServiceForGbImpl implements ISourcePTZService {
|
|||||||
log.error("[FI失败] 未知的光圈指令 {}", frontEndControlCode.getIris());
|
log.error("[FI失败] 未知的光圈指令 {}", frontEndControlCode.getIris());
|
||||||
callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null);
|
callback.run(ErrorCode.ERROR100.getCode(), "未知的指令", null);
|
||||||
}
|
}
|
||||||
if (frontEndControlCode.getIrisSpeed() == null) {
|
|
||||||
callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
irisSpeed = frontEndControlCode.getIrisSpeed();
|
|
||||||
}
|
}
|
||||||
|
if (frontEndControlCode.getFocusSpeed() == null) {
|
||||||
|
callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (frontEndControlCode.getIrisSpeed() == null) {
|
||||||
|
callback.run(ErrorCode.ERROR100.getCode(), "参数异常", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
focusSpeed = frontEndControlCode.getFocusSpeed();
|
||||||
|
irisSpeed = frontEndControlCode.getIrisSpeed();
|
||||||
}
|
}
|
||||||
ptzService.frontEndCommand(channel, cmdCode, focusSpeed, irisSpeed, parameter3);
|
ptzService.frontEndCommand(channel, cmdCode, focusSpeed, irisSpeed, parameter3);
|
||||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
|
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
|
||||||
@ -348,16 +348,4 @@ public class SourcePTZServiceForGbImpl implements ISourcePTZService {
|
|||||||
public void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
public void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
||||||
ptzService.queryPresetList(channel, callback);
|
ptzService.queryPresetList(channel, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom controlCode, ErrorCallback<String> callback) {
|
|
||||||
if (controlCode.getCode() == 1) {
|
|
||||||
ptzService.dragZoomIn(channel, controlCode.getLength(), controlCode.getWidth(), controlCode.getMidPointX(),
|
|
||||||
controlCode.getMidPointY(), controlCode.getLengthX(), controlCode.getLengthY());
|
|
||||||
}else {
|
|
||||||
ptzService.dragZoomOut(channel, controlCode.getLength(), controlCode.getWidth(), controlCode.getMidPointX(),
|
|
||||||
controlCode.getMidPointY(), controlCode.getLengthX(), controlCode.getLengthY());
|
|
||||||
}
|
|
||||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) throws InvalidArgumentException, SipException, ParseException;
|
void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback<String> callback) 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;
|
||||||
|
|||||||
@ -496,7 +496,7 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
}
|
}
|
||||||
if (!mediaServerItem.isRtpEnable()) {
|
if (!mediaServerItem.isRtpEnable()) {
|
||||||
// 单端口暂不支持语音喊话
|
// 单端口暂不支持语音喊话
|
||||||
log.warn("[语音喊话] 单端口暂不支持此操作");
|
log.info("[语音喊话] 单端口暂不支持此操作");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1292,7 +1292,7 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
|
public void dragZoomCmd(Device device, String channelId, String cmdString, ErrorCallback<String> callback) 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,6 +1311,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -241,19 +241,6 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
case DRAG_ZOOM:
|
|
||||||
channelControlService.dragZoom(channel, (FrontEndControlCodeForDragZoom) frontEndControlCode, ((code, msg, data) -> {
|
|
||||||
try {
|
|
||||||
if (code == ErrorCode.SUCCESS.getCode()) {
|
|
||||||
responseAck(request, Response.OK);
|
|
||||||
}else {
|
|
||||||
responseAck(request, Response.FORBIDDEN);
|
|
||||||
}
|
|
||||||
} catch (InvalidArgumentException | SipException | ParseException exception) {
|
|
||||||
log.error("[命令发送失败] 辅助开关指令: {}", exception.getMessage());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
log.info("[INFO 消息] 设备不支持的控制方式");
|
log.info("[INFO 消息] 设备不支持的控制方式");
|
||||||
try {
|
try {
|
||||||
@ -403,7 +390,9 @@ 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());
|
cmder.dragZoomCmd(device, deviceChannel.getDeviceId(), cmdXml.toString(), (code, msg, data) -> {
|
||||||
|
|
||||||
|
});
|
||||||
responseAck(request, Response.OK);
|
responseAck(request, Response.OK);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[命令发送失败] 拉框控制: {}", e.getMessage());
|
log.error("[命令发送失败] 拉框控制: {}", e.getMessage());
|
||||||
|
|||||||
@ -155,9 +155,4 @@ public class SourcePTZServiceForJTImpl implements ISourcePTZService {
|
|||||||
public void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
public void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
|
||||||
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);
|
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback) {
|
|
||||||
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import com.alibaba.fastjson2.JSON;
|
|||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.alibaba.fastjson2.TypeReference;
|
import com.alibaba.fastjson2.TypeReference;
|
||||||
|
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||||
|
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
@ -15,9 +17,11 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -330,6 +334,11 @@ public class ZLMRESTfulUtils {
|
|||||||
|
|
||||||
public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec,
|
public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec,
|
||||||
boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
|
boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
|
||||||
|
try {
|
||||||
|
src_url = URLEncoder.encode(src_url, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> param = new HashMap<>();
|
Map<String, Object> param = new HashMap<>();
|
||||||
param.put("src_url", src_url);
|
param.put("src_url", src_url);
|
||||||
|
|||||||
@ -69,9 +69,9 @@ public interface IRedisRpcService {
|
|||||||
|
|
||||||
WVPResult<String> homePosition(String serverId, Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex);
|
WVPResult<String> homePosition(String serverId, Device device, String channelId, Boolean enabled, Integer resetTime, Integer presetIndex);
|
||||||
|
|
||||||
void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY);
|
void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy);
|
||||||
|
|
||||||
void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY);
|
void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy);
|
||||||
|
|
||||||
WVPResult<String> deviceStatus(String serverId, Device device);
|
WVPResult<String> deviceStatus(String serverId, Device device);
|
||||||
|
|
||||||
|
|||||||
@ -318,10 +318,10 @@ public class RedisRpcDeviceController extends RpcController {
|
|||||||
String channelId = paramJson.getString("channelId");
|
String channelId = paramJson.getString("channelId");
|
||||||
Integer length = paramJson.getInteger("length");
|
Integer length = paramJson.getInteger("length");
|
||||||
Integer width = paramJson.getInteger("width");
|
Integer width = paramJson.getInteger("width");
|
||||||
Integer midPointX = paramJson.getInteger("midPointX");
|
Integer midpointx = paramJson.getInteger("midpointx");
|
||||||
Integer midPointY = paramJson.getInteger("midPointY");
|
Integer midpointy = paramJson.getInteger("midpointy");
|
||||||
Integer lengthX = paramJson.getInteger("lengthX");
|
Integer lengthx = paramJson.getInteger("lengthx");
|
||||||
Integer lengthY = paramJson.getInteger("lengthY");
|
Integer lengthy = paramJson.getInteger("lengthy");
|
||||||
|
|
||||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||||
|
|
||||||
@ -332,15 +332,18 @@ public class RedisRpcDeviceController extends RpcController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
deviceService.dragZoomIn(device, channelId, length, width, midPointX, midPointY, lengthX, lengthY);
|
deviceService.dragZoomIn(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> {
|
||||||
|
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()));
|
||||||
return response;
|
sendResponse(response);
|
||||||
}
|
}
|
||||||
response.setStatusCode(ErrorCode.SUCCESS.getCode());
|
return null;
|
||||||
response.setBody(WVPResult.success());
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RedisRpcMapping("dragZoomOut")
|
@RedisRpcMapping("dragZoomOut")
|
||||||
@ -350,10 +353,10 @@ public class RedisRpcDeviceController extends RpcController {
|
|||||||
String channelId = paramJson.getString("channelId");
|
String channelId = paramJson.getString("channelId");
|
||||||
Integer length = paramJson.getInteger("length");
|
Integer length = paramJson.getInteger("length");
|
||||||
Integer width = paramJson.getInteger("width");
|
Integer width = paramJson.getInteger("width");
|
||||||
Integer midPointX = paramJson.getInteger("midPointX");
|
Integer midpointx = paramJson.getInteger("midpointx");
|
||||||
Integer midPointY = paramJson.getInteger("midPointY");
|
Integer midpointy = paramJson.getInteger("midpointy");
|
||||||
Integer lengthX = paramJson.getInteger("lengthX");
|
Integer lengthx = paramJson.getInteger("lengthx");
|
||||||
Integer lengthY = paramJson.getInteger("lengthY");
|
Integer lengthy = paramJson.getInteger("lengthy");
|
||||||
|
|
||||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||||
|
|
||||||
@ -364,15 +367,18 @@ public class RedisRpcDeviceController extends RpcController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
deviceService.dragZoomOut(device, channelId, length, width, midPointX, midPointY, lengthX, lengthY);
|
deviceService.dragZoomOut(device, channelId, length, width, midpointx, midpointy, lengthx, lengthy, (code, msg, data) -> {
|
||||||
|
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()));
|
||||||
return response;
|
sendResponse(response);
|
||||||
}
|
}
|
||||||
response.setStatusCode(ErrorCode.SUCCESS.getCode());
|
return null;
|
||||||
response.setBody(WVPResult.success());
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RedisRpcMapping("alarm")
|
@RedisRpcMapping("alarm")
|
||||||
|
|||||||
@ -433,33 +433,33 @@ public class RedisRpcServiceImpl implements IRedisRpcService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midPointX,
|
public void dragZoomIn(String serverId, Device device, String channelId, int length, int width, int midpointx,
|
||||||
int midPointY, int lengthX, int lengthY) {
|
int midpointy, int lengthx, int lengthy) {
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
jsonObject.put("device", device.getDeviceId());
|
jsonObject.put("device", device.getDeviceId());
|
||||||
jsonObject.put("channelId", channelId);
|
jsonObject.put("channelId", channelId);
|
||||||
jsonObject.put("length", length);
|
jsonObject.put("length", length);
|
||||||
jsonObject.put("width", width);
|
jsonObject.put("width", width);
|
||||||
jsonObject.put("midPointX", midPointX);
|
jsonObject.put("midpointx", midpointx);
|
||||||
jsonObject.put("midPointY", midPointY);
|
jsonObject.put("midpointy", midpointy);
|
||||||
jsonObject.put("lengthX", lengthX);
|
jsonObject.put("lengthx", lengthx);
|
||||||
jsonObject.put("lengthY", lengthY);
|
jsonObject.put("lengthy", lengthy);
|
||||||
RedisRpcRequest request = buildRequest("device/dragZoomIn", jsonObject);
|
RedisRpcRequest request = buildRequest("device/dragZoomIn", jsonObject);
|
||||||
request.setToId(serverId);
|
request.setToId(serverId);
|
||||||
redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS);
|
redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY) {
|
public void dragZoomOut(String serverId, Device device, String channelId, int length, int width, int midpointx, int midpointy, int lengthx, int lengthy) {
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
jsonObject.put("device", device.getDeviceId());
|
jsonObject.put("device", device.getDeviceId());
|
||||||
jsonObject.put("channelId", channelId);
|
jsonObject.put("channelId", channelId);
|
||||||
jsonObject.put("length", length);
|
jsonObject.put("length", length);
|
||||||
jsonObject.put("width", width);
|
jsonObject.put("width", width);
|
||||||
jsonObject.put("midPointX", midPointX);
|
jsonObject.put("midpointx", midpointx);
|
||||||
jsonObject.put("midPointY", midPointY);
|
jsonObject.put("midpointy", midpointy);
|
||||||
jsonObject.put("lengthX", lengthX);
|
jsonObject.put("lengthx", lengthx);
|
||||||
jsonObject.put("lengthY", lengthY);
|
jsonObject.put("lengthy", lengthy);
|
||||||
RedisRpcRequest request = buildRequest("device/dragZoomOut", jsonObject);
|
RedisRpcRequest request = buildRequest("device/dragZoomOut", jsonObject);
|
||||||
request.setToId(serverId);
|
request.setToId(serverId);
|
||||||
redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS);
|
redisRpcConfig.request(request, 50, TimeUnit.MILLISECONDS);
|
||||||
|
|||||||
@ -1,13 +1,8 @@
|
|||||||
package com.genersoft.iot.vmp.vmanager.bean;
|
package com.genersoft.iot.vmp.vmanager.bean;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lin
|
* @author lin
|
||||||
*/
|
*/
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
public class AudioBroadcastResult {
|
public class AudioBroadcastResult {
|
||||||
/**
|
/**
|
||||||
* 推流的各个方式流地址
|
* 推流的各个方式流地址
|
||||||
@ -29,10 +24,36 @@ public class AudioBroadcastResult {
|
|||||||
*/
|
*/
|
||||||
private String stream;
|
private String stream;
|
||||||
|
|
||||||
/**
|
|
||||||
* 播放流地址(设备音频通过ZLM播放给浏览器),对讲时设置
|
|
||||||
*/
|
|
||||||
private StreamContent playStreamInfo;
|
|
||||||
|
|
||||||
|
public StreamContent getStreamInfo() {
|
||||||
|
return streamInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamInfo(StreamContent streamInfo) {
|
||||||
|
this.streamInfo = streamInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCodec() {
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodec(String codec) {
|
||||||
|
this.codec = codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApp() {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApp(String app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStream() {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStream(String stream) {
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.vmanager.bean;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Schema(description = "对讲信息")
|
|
||||||
public class AudioTalkResult {
|
|
||||||
|
|
||||||
@Schema(description = "推流地址(浏览器 WebRTC推流到ZLM)")
|
|
||||||
private StreamContent pushStream;
|
|
||||||
|
|
||||||
@Schema(description = "播放地址(设备音频通过ZLM播放给浏览器),喊话时为null")
|
|
||||||
private StreamContent playStream;
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.bean;
|
|
||||||
|
|
||||||
import org.dom4j.DocumentHelper;
|
|
||||||
import org.dom4j.Element;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class DeviceAlarmNotifyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fromXml_withoutAlarmType_shouldNotThrowNpe() throws Exception {
|
|
||||||
String xml = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Notify>
|
|
||||||
<DeviceID>55123456781381000010</DeviceID>
|
|
||||||
<AlarmPriority>1</AlarmPriority>
|
|
||||||
<AlarmMethod>7</AlarmMethod>
|
|
||||||
<AlarmTime>2026-06-05T09:46:05</AlarmTime>
|
|
||||||
<AlarmDescription>1001,1780623964994529058,55123456781381000010,25123456781381000050,55LCPCweb10</AlarmDescription>
|
|
||||||
<Longitude>0.0</Longitude>
|
|
||||||
<Latitude>0.0</Latitude>
|
|
||||||
</Notify>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Element root = DocumentHelper.parseText(xml).getRootElement();
|
|
||||||
DeviceAlarmNotify notify = DeviceAlarmNotify.fromXml(root);
|
|
||||||
|
|
||||||
assertNotNull(notify);
|
|
||||||
assertEquals(Integer.valueOf(7), notify.getAlarmMethod());
|
|
||||||
assertNull(notify.getAlarmType(), "AlarmType should be null when not present in XML");
|
|
||||||
|
|
||||||
// Simulate the exact code path from AlarmNotifyMessageHandler.executeTaskQueue lines 131-141
|
|
||||||
// which was causing the NPE
|
|
||||||
AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
alarmChannelMessage.setAlarmType(notify.getAlarmType());
|
|
||||||
alarmChannelMessage.setAlarmSn(notify.getAlarmMethod());
|
|
||||||
alarmChannelMessage.setAlarmDescription(notify.getAlarmDescription());
|
|
||||||
alarmChannelMessage.setGbId(notify.getChannelId());
|
|
||||||
}, "setAlarmType(null) should not throw NPE when field type is Integer");
|
|
||||||
assertNull(alarmChannelMessage.getAlarmType());
|
|
||||||
assertEquals(Integer.valueOf(7), alarmChannelMessage.getAlarmSn());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fromXml_withAlarmType_shouldParseCorrectly() throws Exception {
|
|
||||||
String xml = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Notify>
|
|
||||||
<DeviceID>34020000001320000001</DeviceID>
|
|
||||||
<AlarmPriority>1</AlarmPriority>
|
|
||||||
<AlarmMethod>2</AlarmMethod>
|
|
||||||
<AlarmTime>2026-06-05T10:30:00</AlarmTime>
|
|
||||||
<AlarmDescription>Video loss alarm</AlarmDescription>
|
|
||||||
<Longitude>116.397</Longitude>
|
|
||||||
<Latitude>39.908</Latitude>
|
|
||||||
<AlarmType>1</AlarmType>
|
|
||||||
</Notify>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Element root = DocumentHelper.parseText(xml).getRootElement();
|
|
||||||
DeviceAlarmNotify notify = DeviceAlarmNotify.fromXml(root);
|
|
||||||
|
|
||||||
assertNotNull(notify);
|
|
||||||
assertEquals(Integer.valueOf(2), notify.getAlarmMethod());
|
|
||||||
assertEquals(Integer.valueOf(1), notify.getAlarmType());
|
|
||||||
|
|
||||||
AlarmChannelMessage msg = new AlarmChannelMessage();
|
|
||||||
assertDoesNotThrow(() -> msg.setAlarmType(notify.getAlarmType()));
|
|
||||||
assertEquals(Integer.valueOf(1), msg.getAlarmType());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fromXml_withAlarmTypeInInfo_shouldUseInfoValue() throws Exception {
|
|
||||||
String xml = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Notify>
|
|
||||||
<DeviceID>34020000001320000001</DeviceID>
|
|
||||||
<AlarmPriority>1</AlarmPriority>
|
|
||||||
<AlarmMethod>5</AlarmMethod>
|
|
||||||
<AlarmTime>2026-06-05T10:30:00</AlarmTime>
|
|
||||||
<AlarmDescription>Motion detection</AlarmDescription>
|
|
||||||
<Longitude>116.397</Longitude>
|
|
||||||
<Latitude>39.908</Latitude>
|
|
||||||
<AlarmType>9</AlarmType>
|
|
||||||
<Info>
|
|
||||||
<AlarmType>2</AlarmType>
|
|
||||||
</Info>
|
|
||||||
</Notify>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Element root = DocumentHelper.parseText(xml).getRootElement();
|
|
||||||
DeviceAlarmNotify notify = DeviceAlarmNotify.fromXml(root);
|
|
||||||
|
|
||||||
assertNotNull(notify);
|
|
||||||
assertEquals(Integer.valueOf(2), notify.getAlarmType(),
|
|
||||||
"AlarmType should use Info/AlarmType value when present");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.jt1078.dao.provider;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class JTChannelProviderTest {
|
|
||||||
|
|
||||||
private final JTChannelProvider provider = new JTChannelProvider();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withQuery_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("terminalDbId", 1);
|
|
||||||
params.put("query", "test-channel");
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("#{query}"), "should use #{query} bind variable");
|
|
||||||
assertFalse(sql.contains("test-channel"), "should not contain raw query value");
|
|
||||||
assertTrue(sql.contains("concat('%',#{query},'%')"), "should use concat with bind variable");
|
|
||||||
assertTrue(sql.contains("#{terminalDbId}"), "should use #{terminalDbId} bind variable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withoutQuery_shouldNotContainLike() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("terminalDbId", 1);
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertFalse(sql.contains("LIKE"), "should not contain LIKE clause when no query");
|
|
||||||
assertTrue(sql.contains("#{terminalDbId}"), "should still have terminalDbId condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectChannelByChannelId_shouldUseBindVariables() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("terminalDbId", 5);
|
|
||||||
params.put("channelId", 100);
|
|
||||||
String sql = provider.selectChannelByChannelId(params);
|
|
||||||
assertTrue(sql.contains("#{terminalDbId}"), "should use #{terminalDbId}");
|
|
||||||
assertTrue(sql.contains("#{channelId}"), "should use #{channelId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectChannelById_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("id", 42);
|
|
||||||
String sql = provider.selectChannelById(params);
|
|
||||||
assertTrue(sql.contains("#{id}"), "should use #{id} bind variable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_shouldOrderByChannelId() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("terminalDbId", 1);
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("ORDER BY jc.channel_id"), "should order by channel_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void baseSql_shouldHaveJoins() {
|
|
||||||
assertTrue(JTChannelProvider.BASE_SQL.contains("LEFT join wvp_device_channel"), "should have LEFT JOIN");
|
|
||||||
assertTrue(JTChannelProvider.BASE_SQL.contains("wvp_jt_channel"), "should query from jt_channel");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
package com.genersoft.iot.vmp.streamProxy.dao.provider;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class StreamProxyProviderTest {
|
|
||||||
|
|
||||||
private final StreamProxyProvider provider = new StreamProxyProvider();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void select_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("id", 123);
|
|
||||||
String sql = provider.select(params);
|
|
||||||
assertTrue(sql.contains("#{id}"), "should use #{id} bind variable");
|
|
||||||
assertFalse(sql.contains("123"), "should not contain raw value");
|
|
||||||
assertTrue(sql.contains("WHERE st.id = #{id}"), "should have proper WHERE clause");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectOneByAppAndStream_shouldUseBindVariables() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("app", "testApp");
|
|
||||||
params.put("stream", "testStream");
|
|
||||||
String sql = provider.selectOneByAppAndStream(params);
|
|
||||||
assertTrue(sql.contains("#{app}"), "should use #{app} bind variable");
|
|
||||||
assertTrue(sql.contains("#{stream}"), "should use #{stream} bind variable");
|
|
||||||
assertFalse(sql.contains("testApp"), "should not contain raw app value");
|
|
||||||
assertFalse(sql.contains("testStream"), "should not contain raw stream value");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectForPushingInMediaServer_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("mediaServerId", "server-001");
|
|
||||||
String sql = provider.selectForPushingInMediaServer(params);
|
|
||||||
assertTrue(sql.contains("#{mediaServerId}"), "should use #{mediaServerId} bind variable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withQuery_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("query", "test-query");
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("#{query}"), "should use #{query} bind variable");
|
|
||||||
assertFalse(sql.contains("test-query"), "should not contain raw query value");
|
|
||||||
assertTrue(sql.contains("LIKE concat('%',#{query},'%')"), "should use concat with bind variable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withMediaServerId_shouldUseBindVariable() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("mediaServerId", "server-001");
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("#{mediaServerId}"), "should use #{mediaServerId} bind variable");
|
|
||||||
assertFalse(sql.contains("server-001"), "should not contain raw server id");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withPullingTrue() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("pulling", true);
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("st.pulling=1"), "should filter by pulling=1");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withPullingFalse() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("pulling", false);
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("st.pulling=0"), "should filter by pulling=0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void selectAll_withoutParams_shouldReturnBaseQuery() {
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
String sql = provider.selectAll(params);
|
|
||||||
assertTrue(sql.contains("FROM wvp_stream_proxy"), "should have FROM clause");
|
|
||||||
assertTrue(sql.contains("LEFT join wvp_device_channel"), "should have JOIN clause");
|
|
||||||
assertTrue(sql.contains("order by"), "should have ORDER BY");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getBaseSelectSql_shouldReturnValidSql() {
|
|
||||||
String sql = provider.getBaseSelectSql();
|
|
||||||
assertTrue(sql.contains("SELECT"), "should start with SELECT");
|
|
||||||
assertTrue(sql.contains("FROM wvp_stream_proxy"), "should have FROM");
|
|
||||||
assertTrue(sql.contains("LEFT join wvp_device_channel"), "should have LEFT JOIN");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -270,38 +270,6 @@ export function stopPlayChannel(channelId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function talkStart(channelId) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/talk/start',
|
|
||||||
params: { channelId }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function talkStop(channelId) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/talk/stop',
|
|
||||||
params: { channelId }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function broadcastStart(channelId) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/broadcast/start',
|
|
||||||
params: { channelId }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function broadcastStop(channelId) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/broadcast/stop',
|
|
||||||
params: { channelId }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 前端控制
|
// 前端控制
|
||||||
|
|
||||||
@ -544,20 +512,6 @@ export function focus({ channelId, command, speed }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function dragZoomIn(params) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/front-end/drag_zoom_in',
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export function dragZoomOut(params) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/common/channel/front-end/drag_zoom_out',
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export function queryRecord({ channelId, startTime, endTime }) {
|
export function queryRecord({ channelId, startTime, endTime }) {
|
||||||
return request({
|
return request({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
|||||||
@ -69,14 +69,6 @@ export function resetGuard(deviceId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function homePosition(params) {
|
|
||||||
return request({
|
|
||||||
method: 'get',
|
|
||||||
url: '/api/device/control/home_position',
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function subscribeCatalog(params) {
|
export function subscribeCatalog(params) {
|
||||||
const { id, cycle } = params
|
const { id, cycle } = params
|
||||||
return request({
|
return request({
|
||||||
@ -283,19 +275,3 @@ 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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export function wiper([deviceId, channelDeviceId, command]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ptz({ deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed }) {
|
export function ptz([deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed]) {
|
||||||
return request({
|
return request({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `/api/front-end/ptz/${deviceId}/${channelId}`,
|
url: `/api/front-end/ptz/${deviceId}/${channelId}`,
|
||||||
|
|||||||
@ -1,208 +0,0 @@
|
|||||||
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 }
|
|
||||||
console.log('[dragZoom mousedown] getVideoRect:', JSON.stringify(videoRect), 'clientX/Y:', e.clientX, e.clientY, 'dragStart:', JSON.stringify(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
|
|
||||||
}
|
|
||||||
console.log('[dragZoom dragEnd] sx:', sx, 'sy:', sy, 'ex:', ex, 'ey:', ey, 'rectW:', rectW, 'rectH:', rectH)
|
|
||||||
if (this.dragCallback) {
|
|
||||||
const params = {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
console.log('[dragZoom dragEnd] callback params:', JSON.stringify(params))
|
|
||||||
this.dragCallback(params)
|
|
||||||
}
|
|
||||||
this._resetDrag()
|
|
||||||
},
|
|
||||||
_resetDrag() {
|
|
||||||
this._unbindDragEvents()
|
|
||||||
this.dragActive = false
|
|
||||||
this.dragStart = null
|
|
||||||
this.dragCurrent = null
|
|
||||||
this.dragVideoRect = null
|
|
||||||
this.dragCallback = null
|
|
||||||
this._removeCanvas()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -75,7 +75,7 @@ export const constantRoutes = [
|
|||||||
path: '/channel',
|
path: '/channel',
|
||||||
name: 'Channel',
|
name: 'Channel',
|
||||||
component: () => import('@/views/channel/index'),
|
component: () => import('@/views/channel/index'),
|
||||||
meta: { title: '通道列表', icon: 'channelManger' }
|
meta: { title: '通道列表', icon: 'channelManger'}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/channel/record/:channelId',
|
path: '/channel/record/:channelId',
|
||||||
|
|||||||
@ -48,9 +48,7 @@ import {
|
|||||||
stopPlayback,
|
stopPlayback,
|
||||||
pausePlayback,
|
pausePlayback,
|
||||||
resumePlayback,
|
resumePlayback,
|
||||||
seekPlayback, speedPlayback, getAllForMap, test, saveLevel, resetLevel, clearThin, thinProgress, drawThin, saveThin,
|
seekPlayback, speedPlayback, getAllForMap, test, saveLevel, resetLevel, clearThin, thinProgress, drawThin, saveThin
|
||||||
dragZoomIn, dragZoomOut,
|
|
||||||
talkStart, talkStop, broadcastStart, broadcastStop
|
|
||||||
} from '@/api/commonChannel'
|
} from '@/api/commonChannel'
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -284,46 +282,6 @@ const actions = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
talkStart({ commit }, channelId) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
talkStart(channelId).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
talkStop({ commit }, channelId) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
talkStop(channelId).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
broadcastStart({ commit }, channelId) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
broadcastStart(channelId).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
broadcastStop({ commit }, channelId) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
broadcastStop(channelId).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getList({ commit }, param) {
|
getList({ commit }, param) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getList(param).then(response => {
|
getList(param).then(response => {
|
||||||
@ -534,26 +492,6 @@ const actions = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
queryRecord({ commit }, params) {
|
queryRecord({ commit }, params) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
queryRecord(params).then(response => {
|
queryRecord(params).then(response => {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import {
|
|||||||
changeChannelAudio,
|
changeChannelAudio,
|
||||||
deleteDevice,
|
deleteDevice,
|
||||||
deviceRecord, getKeepaliveTimeStatistics, getRegisterTimeStatistics,
|
deviceRecord, getKeepaliveTimeStatistics, getRegisterTimeStatistics,
|
||||||
homePosition,
|
|
||||||
queryBasicParam,
|
queryBasicParam,
|
||||||
queryChannelOne,
|
queryChannelOne,
|
||||||
queryChannels,
|
queryChannels,
|
||||||
@ -84,16 +83,6 @@ const actions = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
homePosition({ commit }, params) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
homePosition(params).then(response => {
|
|
||||||
const { data } = response
|
|
||||||
resolve(data)
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
subscribeCatalog({ commit }, params) {
|
subscribeCatalog({ commit }, params) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
subscribeCatalog(params).then(response => {
|
subscribeCatalog(params).then(response => {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ 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) {
|
||||||
@ -209,26 +208,6 @@ 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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 1291092 */
|
font-family: "iconfont"; /* Project id 1291092 */
|
||||||
src: url('iconfont.woff2?t=1780559263294') format('woff2');
|
src: url('iconfont.woff2?t=1769409737891') format('woff2')
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -11,54 +11,6 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-dingshirenwuguanli:before {
|
|
||||||
content: "\e800";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-zhongqi:before {
|
|
||||||
content: "\e801";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yunfuyangjiaojiao:before {
|
|
||||||
content: "\e7ff";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-free:before {
|
|
||||||
content: "\e7fd";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-Union-32:before {
|
|
||||||
content: "\e7fe";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-ruanxianwei:before {
|
|
||||||
content: "\e7fc";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-sudu:before {
|
|
||||||
content: "\e7fb";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shuipingxuanzhuan:before {
|
|
||||||
content: "\e7fa";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-cengdie:before {
|
|
||||||
content: "\e7f9";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yinpin:before {
|
|
||||||
content: "\e7f6";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xiangjishezhi2:before {
|
|
||||||
content: "\e7f7";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-cengdie3:before {
|
|
||||||
content: "\e815";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xintiao:before {
|
.icon-xintiao:before {
|
||||||
content: "\e7f4";
|
content: "\e7f4";
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -161,11 +161,11 @@
|
|||||||
<i class="el-icon-warning-outline" style="font-size: 32px;" />
|
<i class="el-icon-warning-outline" style="font-size: 32px;" />
|
||||||
<div style="margin-top: 10px;">{{ playbackError }}</div>
|
<div style="margin-top: 10px;">{{ playbackError }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="playbackStreamInfo" style="height: 400px;">
|
<div v-else-if="playbackStreamInfo">
|
||||||
<playerTabs
|
<h265web
|
||||||
ref="playbackPlayer"
|
ref="playbackPlayer"
|
||||||
|
:height="'400px'"
|
||||||
:show-button="false"
|
:show-button="false"
|
||||||
:showTab="true"
|
|
||||||
:has-audio="true"
|
:has-audio="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -177,7 +177,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import playerTabs from '../common/playerTabs.vue'
|
import h265web from '../common/h265web.vue'
|
||||||
|
|
||||||
const ALARM_TYPE_OPTIONS = [
|
const ALARM_TYPE_OPTIONS = [
|
||||||
{ value: 'VideoLoss', label: '视频丢失报警' },
|
{ value: 'VideoLoss', label: '视频丢失报警' },
|
||||||
@ -221,7 +221,7 @@ function formatDatetime(ts) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AlarmManage',
|
name: 'AlarmManage',
|
||||||
components: { playerTabs },
|
components: { h265web },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
alarmList: [],
|
alarmList: [],
|
||||||
@ -238,6 +238,7 @@ export default {
|
|||||||
playbackLoading: false,
|
playbackLoading: false,
|
||||||
playbackError: null,
|
playbackError: null,
|
||||||
playbackStreamInfo: null,
|
playbackStreamInfo: null,
|
||||||
|
playbackVideoUrl: null,
|
||||||
playbackTitle: '录像回放',
|
playbackTitle: '录像回放',
|
||||||
currentPlaybackChannelId: null
|
currentPlaybackChannelId: null
|
||||||
}
|
}
|
||||||
@ -307,10 +308,15 @@ export default {
|
|||||||
endTime: endTime
|
endTime: endTime
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
this.playbackStreamInfo = data
|
this.playbackStreamInfo = data
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
this.playbackVideoUrl = data['wss_flv']
|
||||||
|
} else {
|
||||||
|
this.playbackVideoUrl = data['ws_flv']
|
||||||
|
}
|
||||||
this.playbackLoading = false
|
this.playbackLoading = false
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.playbackPlayer) {
|
if (this.$refs.playbackPlayer) {
|
||||||
this.$refs.playbackPlayer.setStreamInfo(data)
|
this.$refs.playbackPlayer.play(this.playbackVideoUrl)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
@ -320,9 +326,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
closePlayback() {
|
closePlayback() {
|
||||||
if (this.$refs.playbackPlayer) {
|
|
||||||
this.$refs.playbackPlayer.stop()
|
|
||||||
}
|
|
||||||
if (this.playbackStreamInfo && this.currentPlaybackChannelId) {
|
if (this.playbackStreamInfo && this.currentPlaybackChannelId) {
|
||||||
this.$store.dispatch('commonChanel/stopPlayback', {
|
this.$store.dispatch('commonChanel/stopPlayback', {
|
||||||
channelId: this.currentPlaybackChannelId,
|
channelId: this.currentPlaybackChannelId,
|
||||||
@ -333,6 +336,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.playbackDialogVisible = false
|
this.playbackDialogVisible = false
|
||||||
this.playbackStreamInfo = null
|
this.playbackStreamInfo = null
|
||||||
|
this.playbackVideoUrl = null
|
||||||
this.playbackError = null
|
this.playbackError = null
|
||||||
this.currentPlaybackChannelId = null
|
this.currentPlaybackChannelId = null
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,448 +0,0 @@
|
|||||||
<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 && !talkMode">喊话中</span>
|
|
||||||
<span v-if="talkStatus === 1 && talkMode && !playConnected">等待接通中...</span>
|
|
||||||
<span v-if="talkStatus === 1 && talkMode && playConnected">对讲中</span>
|
|
||||||
</p>
|
|
||||||
<p v-if="talkStatus === 1 && talkMode && talkAudioFailed" style="margin-top: 8px;">
|
|
||||||
<el-button
|
|
||||||
type="warning"
|
|
||||||
size="mini"
|
|
||||||
icon="el-icon-refresh"
|
|
||||||
@click="retryTalkAudio"
|
|
||||||
>重试音频</el-button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import playerTabs from '../common/playerTabs.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ChAudioTalk',
|
|
||||||
components: { playerTabs },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showDialog: false,
|
|
||||||
showPlayer: false,
|
|
||||||
previewLoading: false,
|
|
||||||
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(channelId) {
|
|
||||||
if (this.showDialog) return
|
|
||||||
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('commonChanel/playChannel', 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.transcodeStream || 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) {
|
|
||||||
if (this.talkMode && !this.playConnected) return 'warning'
|
|
||||||
return 'danger'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async talkButtonClick() {
|
|
||||||
if (this.talkStatus === -1) {
|
|
||||||
await this.startTalk()
|
|
||||||
} else if (this.talkStatus === 1) {
|
|
||||||
this.stopTalk()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async startTalk() {
|
|
||||||
try {
|
|
||||||
await this.checkMicrophoneAvailability()
|
|
||||||
} catch (e) {
|
|
||||||
this.$message({ showClose: true, message: this.getMicrophoneErrorMessage(e), type: 'error' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.talkStatus = 0
|
|
||||||
|
|
||||||
try {
|
|
||||||
const storeName = 'commonChanel'
|
|
||||||
const actionName = this.talkMode ? 'talkStart' : 'broadcastStart'
|
|
||||||
const data = await this.$store.dispatch(storeName + '/' + actionName, this.channelId)
|
|
||||||
|
|
||||||
const pushStream = data?.pushStream
|
|
||||||
const playStream = data?.playStream
|
|
||||||
|
|
||||||
if (this.talkMode && playStream) {
|
|
||||||
this.talkAudioPlayStream = playStream
|
|
||||||
this.startTalkAudioPlay(playStream)
|
|
||||||
this.muteVideoPlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startWebrtcPush(pushStream)
|
|
||||||
} catch (e) {
|
|
||||||
this.$message({ showClose: true, message: e, type: 'error' })
|
|
||||||
this.talkStatus = -1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
muteVideoPlayer() {
|
|
||||||
const player = this.$refs.playerTabs
|
|
||||||
if (!player) return
|
|
||||||
if (player.mute) {
|
|
||||||
player.mute()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmuteVideoPlayer() {
|
|
||||||
const player = this.$refs.playerTabs
|
|
||||||
if (!player) return
|
|
||||||
if (player.cancelMute) {
|
|
||||||
player.cancelMute()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getMicrophoneErrorMessage(error) {
|
|
||||||
if (!error || !error.name) return '本地麦克风检测失败,请检查浏览器音频采集权限'
|
|
||||||
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError' || error.name === 'SecurityError') {
|
|
||||||
return '未授予浏览器麦克风权限,无法发起语音对讲'
|
|
||||||
}
|
|
||||||
if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
|
|
||||||
return '未检测到可用麦克风,无法发起语音对讲'
|
|
||||||
}
|
|
||||||
if (error.name === 'NotReadableError' || error.name === 'TrackStartError' || error.name === 'AbortError') {
|
|
||||||
return '本地麦克风被占用或暂不可用,请检查后重试'
|
|
||||||
}
|
|
||||||
if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
|
|
||||||
return '当前麦克风不满足采集条件,无法发起语音对讲'
|
|
||||||
}
|
|
||||||
return '本地麦克风检测失败: ' + (error.message || error.name)
|
|
||||||
},
|
|
||||||
async checkMicrophoneAvailability() {
|
|
||||||
if (!window.isSecureContext && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
|
|
||||||
throw new Error('当前页面不是安全上下文,浏览器无法采集麦克风音频')
|
|
||||||
}
|
|
||||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
||||||
throw new Error('当前浏览器不支持麦克风采集')
|
|
||||||
}
|
|
||||||
let stream = null
|
|
||||||
try {
|
|
||||||
stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
|
|
||||||
const audioTracks = stream.getAudioTracks()
|
|
||||||
if (!audioTracks.length) throw new Error('未检测到有效的麦克风音轨')
|
|
||||||
if (audioTracks.every(track => track.readyState === 'ended')) {
|
|
||||||
throw new Error('麦克风已断开或不可用')
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (stream) stream.getTracks().forEach(t => t.stop())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startWebrtcPush(pushStream) {
|
|
||||||
if (!pushStream) return
|
|
||||||
let url = pushStream.rtc || pushStream.rtcs
|
|
||||||
if (!url) {
|
|
||||||
console.warn('[ChAudioTalk] 未找到RTC推流地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('user/getUserInfo').then(user => {
|
|
||||||
if (user && user.pushKey) {
|
|
||||||
url += '&sign=' + user.pushKey
|
|
||||||
} else {
|
|
||||||
console.warn('[ChAudioTalk] 未获取到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.$message({ showClose: true, message: '不支持WebRTC, 无法进行语音对讲', type: 'error' })
|
|
||||||
this.talkStatus = -1
|
|
||||||
})
|
|
||||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, () => {
|
|
||||||
this.$message({ showClose: true, message: 'ICE协商出错', type: 'error' })
|
|
||||||
this.talkStatus = -1
|
|
||||||
})
|
|
||||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, () => {
|
|
||||||
this.$message({ showClose: true, message: 'offer/answer交换失败', type: 'error' })
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).catch(e => {
|
|
||||||
console.warn('[ChAudioTalk] 获取用户pushKey失败', e)
|
|
||||||
this.talkStatus = -1
|
|
||||||
})
|
|
||||||
},
|
|
||||||
startTalkAudioPlay(playStream) {
|
|
||||||
if (this.talkAudioRtc) {
|
|
||||||
this.talkAudioRtc.close()
|
|
||||||
}
|
|
||||||
if (this.talkAudioRetryTimer) {
|
|
||||||
clearTimeout(this.talkAudioRetryTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = location.protocol === 'https:' ? playStream.rtcs : playStream.rtc
|
|
||||||
if (!url) {
|
|
||||||
console.warn('[ChAudioTalk] 无可用的设备音频播放地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.pollMediaInfoAndPlay(playStream)
|
|
||||||
}, 800)
|
|
||||||
},
|
|
||||||
async pollMediaInfoAndPlay(playStream) {
|
|
||||||
try {
|
|
||||||
const data = await this.$store.dispatch('server/getMediaInfo', {
|
|
||||||
app: playStream.app,
|
|
||||||
stream: playStream.stream,
|
|
||||||
mediaServerId: playStream.mediaServerId
|
|
||||||
})
|
|
||||||
if (data) {
|
|
||||||
const url = location.protocol === 'https:' ? playStream.rtcs : playStream.rtc
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
} else {
|
|
||||||
throw new Error('no data')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (this.talkStatus === 1 || this.talkStatus === 0) {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.pollMediaInfoAndPlay(playStream)
|
|
||||||
}, 800)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startTalkAudioByRtc(url) {
|
|
||||||
this.talkAudioFailed = false
|
|
||||||
this.talkAudioRtc = new ZLMRTCClient.Endpoint({
|
|
||||||
debug: false,
|
|
||||||
element: document.getElementById('audioTalkVideo'),
|
|
||||||
zlmsdpUrl: url,
|
|
||||||
simulecast: false,
|
|
||||||
useCamera: false,
|
|
||||||
audioEnable: true,
|
|
||||||
videoEnable: false,
|
|
||||||
recvOnly: true,
|
|
||||||
usedatachannel: false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {
|
|
||||||
console.warn('[ChAudioTalk] 播放流offer失败:', e?.code, e?.msg)
|
|
||||||
if (e && e.code == -400 && e.msg == '流不存在') {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, () => {
|
|
||||||
console.warn('[ChAudioTalk] 设备音频流到达')
|
|
||||||
this.playConnected = true
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, () => {
|
|
||||||
console.error('[ChAudioTalk] 音频播放ICE协商失败')
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (s) => {
|
|
||||||
console.warn('[ChAudioTalk] 音频播放连接状态:', s)
|
|
||||||
if (s === 'connected') {
|
|
||||||
this.playConnected = true
|
|
||||||
} else if (s === 'disconnected' || s === 'failed' || s === 'closed') {
|
|
||||||
this.playConnected = false
|
|
||||||
this.talkAudioFailed = true
|
|
||||||
if (this.talkStatus === 1) {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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
|
|
||||||
this.unmuteVideoPlayer()
|
|
||||||
|
|
||||||
const storeName = 'commonChanel'
|
|
||||||
const actionName = this.talkMode ? 'talkStop' : 'broadcastStop'
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch(storeName + '/' + actionName, this.channelId)
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('停止对讲失败', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.talkStatus = -1
|
|
||||||
},
|
|
||||||
retryTalkAudio() {
|
|
||||||
if (this.talkAudioPlayStream) {
|
|
||||||
this.startTalkAudioPlay(this.talkAudioPlayStream)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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>
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="ptz-section">
|
|
||||||
<ptzControls
|
|
||||||
btn-layout="row"
|
|
||||||
@ptz-move="onPtzMove"
|
|
||||||
@ptz-stop="onPtzStop"
|
|
||||||
@focus-move="onFocusMove"
|
|
||||||
@focus-stop="onFocusStop"
|
|
||||||
@iris-move="onIrisMove"
|
|
||||||
@iris-stop="onIrisStop"
|
|
||||||
@toggle-drag-zoom="$emit('drag-zoom-start', 'in')"
|
|
||||||
@toggle-drag-zoom-out="$emit('drag-zoom-start', 'out')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ptzControls from '../../common/ptzControls.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ChannelPtzPanel',
|
|
||||||
components: { ptzControls },
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onPtzMove(e) {
|
|
||||||
this.$store.dispatch('commonChanel/ptz', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: e.direction,
|
|
||||||
panSpeed: e.speed,
|
|
||||||
tiltSpeed: e.speed,
|
|
||||||
zoomSpeed: e.speed
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onPtzStop() {
|
|
||||||
this.$store.dispatch('commonChanel/ptz', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: 'stop',
|
|
||||||
panSpeed: 0,
|
|
||||||
tiltSpeed: 0,
|
|
||||||
zoomSpeed: 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onFocusMove(e) {
|
|
||||||
this.$store.dispatch('commonChanel/focus', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: e.command,
|
|
||||||
speed: e.speed
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onFocusStop() {
|
|
||||||
this.$store.dispatch('commonChanel/focus', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: 'stop',
|
|
||||||
speed: 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onIrisMove(e) {
|
|
||||||
this.$store.dispatch('commonChanel/iris', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: e.command,
|
|
||||||
speed: e.speed
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onIrisStop() {
|
|
||||||
this.$store.dispatch('commonChanel/iris', {
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: 'stop',
|
|
||||||
speed: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.ptz-section {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-ptz-panel">
|
|
||||||
<div class="player-section">
|
|
||||||
<div class="player-wrapper" :style="{ height: playerHeight }">
|
|
||||||
<playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<channelPtzPanel
|
|
||||||
style="margin-top: 5vh"
|
|
||||||
:channel-id="channelId"
|
|
||||||
@drag-zoom-start="toggleDragZoom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import playerTabs from '../../common/playerTabs.vue'
|
|
||||||
import channelPtzPanel from './channelPtzPanel.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ChPlayerPtzPanel',
|
|
||||||
components: { playerTabs, channelPtzPanel },
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
hasAudio: false,
|
|
||||||
playerHeight: '40vh',
|
|
||||||
dragZoomDirection: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.startPlay()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.stopPlay()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
startPlay() {
|
|
||||||
this.$store.dispatch('commonChanel/playChannel', this.channelId)
|
|
||||||
.then(data => {
|
|
||||||
this.hasAudio = data.hasAudio
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.setStreamInfo(data.transcodeStream || data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
this.$message({ showClose: true, message: e || '播放失败', type: 'error' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopPlay() {
|
|
||||||
this.$store.dispatch('commonChanel/stopPlayChannel', this.channelId)
|
|
||||||
.catch(() => {})
|
|
||||||
},
|
|
||||||
toggleDragZoom(direction) {
|
|
||||||
this.dragZoomDirection = direction
|
|
||||||
this.$refs.playerTabs.startDragZoom((params) => {
|
|
||||||
params.deviceId = this.channelId
|
|
||||||
params.channelId = this.channelId
|
|
||||||
const action = this.dragZoomDirection === 'in' ? 'commonChanel/dragZoomIn' : 'commonChanel/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 = ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.player-ptz-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.player-section {
|
|
||||||
flex: 0.8;
|
|
||||||
}
|
|
||||||
.player-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,269 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzCruiseConfig" style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :disabled="formVisible" @click="openAdd">添加巡航组</el-button>
|
|
||||||
<el-button :loading="clearing" :disabled="clearing" @click="clearCruiseTours">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle @click="loadPresets" />
|
|
||||||
</div>
|
|
||||||
<div v-if="formVisible" style="margin-bottom: 6px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px;">
|
|
||||||
<el-form inline size="small" style="display: flex; align-items: center; margin-top: 15px;">
|
|
||||||
<el-form-item label="序号" style="margin-bottom: 0;">
|
|
||||||
<el-input-number v-model="formId" :min="0" :max="255" controls-position="right" style="width: 120px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="名称" style="margin-bottom: 0;">
|
|
||||||
<el-input v-model="formName" placeholder="名称" style="width: 140px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmSave">确定</el-button>
|
|
||||||
<el-button @click="cancelForm">取消</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-divider style="margin: 6px 0;" />
|
|
||||||
<div style="margin-bottom: 4px;">
|
|
||||||
<el-button size="mini" type="primary" @click="addPresetRow">添加预置点</el-button>
|
|
||||||
</div>
|
|
||||||
<el-table :data="formPresets" size="mini" stripe border max-height="200px">
|
|
||||||
<el-table-column label="序号" width="50">
|
|
||||||
<template v-slot="{ $index }">{{ $index + 1 }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="预置点" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.presetId" size="mini" style="width: 120px" placeholder="选择预置点">
|
|
||||||
<el-option v-for="p in allPresetList" :key="p.presetId"
|
|
||||||
:label="p.presetName || ('预置点' + p.presetId)"
|
|
||||||
:value="p.presetId" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="停留时间(秒)" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-input-number v-model="row.dwellTime" :min="15" :max="300" size="mini" controls-position="right" style="width: 90px" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="速度" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.speed" size="mini" style="width: 90px">
|
|
||||||
<el-option v-for="s in 10" :key="s" :label="s" :value="s" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="60">
|
|
||||||
<template v-slot="{ $index }">
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" @click="removePresetRow($index)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-if="cruiseTours.length > 0" style="flex: 1; overflow: auto;">
|
|
||||||
<el-table ref="cruiseTable" :data="cruiseTours" size="mini" max-height="100%" stripe border highlight-current-row>
|
|
||||||
<el-table-column prop="id" label="ID" />
|
|
||||||
<el-table-column prop="name" label="巡航名称" />
|
|
||||||
<el-table-column label="操作" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<el-button v-if="cruisingCruiseId === scope.row.id" size="mini" type="text" style="color: #F56C6C" :loading="operatingId === scope.row.id" :disabled="operatingId !== null" @click="stopCruise(scope.row)">停用</el-button>
|
|
||||||
<el-button v-else size="mini" type="text" :disabled="cruisingCruiseId !== null || operatingId !== null" style="color: #409EFF" :loading="operatingId === scope.row.id" @click="startCruise(scope.row)">启用</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #409EFF" :disabled="operatingId !== null" @click="openEdit(scope.row)">编辑</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" :loading="deletingId === scope.row.id" :disabled="operatingId !== null || deletingId !== null" @click="deleteCruise(scope.row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else style="color: #909399; font-size: 12px; margin-bottom: 8px;">暂无巡航路线</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzCruiseConfig',
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
cruiseTours: [],
|
|
||||||
cruisingCruiseId: null,
|
|
||||||
formVisible: false,
|
|
||||||
editingTourId: null,
|
|
||||||
submitting: false,
|
|
||||||
clearing: false,
|
|
||||||
operatingId: null,
|
|
||||||
deletingId: null,
|
|
||||||
formId: 1,
|
|
||||||
formName: '',
|
|
||||||
formPresets: [],
|
|
||||||
allPresetList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadPresets()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadPresets() {
|
|
||||||
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
|
|
||||||
.then(data => {
|
|
||||||
this.allPresetList = data || []
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('[巡航] 加载预置点列表失败', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getNextAvailableId() {
|
|
||||||
const used = new Set((this.cruiseTours || []).map(t => t.id))
|
|
||||||
for (let i = 0; i <= 255; i++) {
|
|
||||||
if (!used.has(i)) return i
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
openAdd() {
|
|
||||||
this.editingTourId = null
|
|
||||||
this.formId = this.getNextAvailableId()
|
|
||||||
this.formName = '巡航组' + this.formId
|
|
||||||
this.formPresets = []
|
|
||||||
this.formVisible = true
|
|
||||||
},
|
|
||||||
openEdit(tour) {
|
|
||||||
this.editingTourId = tour.id
|
|
||||||
this.formId = tour.id
|
|
||||||
this.formName = tour.name
|
|
||||||
this.formPresets = (tour.presets || []).map(p => ({
|
|
||||||
presetId: p.presetId,
|
|
||||||
dwellTime: p.dwellTime,
|
|
||||||
speed: p.speed
|
|
||||||
}))
|
|
||||||
if (this.formPresets.length === 0) {
|
|
||||||
this.formPresets.push({ presetId: this.getFirstPresetId(), dwellTime: 15, speed: 7 })
|
|
||||||
}
|
|
||||||
this.formVisible = true
|
|
||||||
},
|
|
||||||
cancelForm() {
|
|
||||||
this.formVisible = false
|
|
||||||
this.editingTourId = null
|
|
||||||
this.formPresets = []
|
|
||||||
},
|
|
||||||
getFirstPresetId() {
|
|
||||||
const first = this.allPresetList[0]
|
|
||||||
return first ? first.presetId : 1
|
|
||||||
},
|
|
||||||
addPresetRow() {
|
|
||||||
this.formPresets.push({
|
|
||||||
presetId: this.getFirstPresetId(),
|
|
||||||
dwellTime: 15,
|
|
||||||
speed: 7
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removePresetRow(index) {
|
|
||||||
this.formPresets.splice(index, 1)
|
|
||||||
},
|
|
||||||
confirmSave() {
|
|
||||||
if (!this.formName.trim()) {
|
|
||||||
this.$message({ showClose: true, message: '请输入巡航组名称', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.formId === null || this.formId < 0 || this.formId > 255) {
|
|
||||||
this.$message({ showClose: true, message: '巡航序号必须在0-255之间', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.submitting = true
|
|
||||||
const cid = this.channelId
|
|
||||||
const fid = this.formId
|
|
||||||
let chain = Promise.resolve()
|
|
||||||
if (this.editingTourId !== null) {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('commonChanel/deletePointForCruise', { channelId: cid, tourId: fid, presetId: 0 }))
|
|
||||||
}
|
|
||||||
this.formPresets.forEach(p => {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('commonChanel/addPointForCruise', { channelId: cid, tourId: fid, presetId: p.presetId }))
|
|
||||||
})
|
|
||||||
const speed = this.formPresets.length > 0 ? this.formPresets[0].speed : 7
|
|
||||||
const dwellTime = this.formPresets.length > 0 ? this.formPresets[0].dwellTime : 15
|
|
||||||
chain = chain.then(() => this.$store.dispatch('commonChanel/setCruiseSpeed', { channelId: cid, tourId: fid, presetId: 0, speed }))
|
|
||||||
chain = chain.then(() => this.$store.dispatch('commonChanel/setCruiseTime', { channelId: cid, tourId: fid, presetId: 0, time: dwellTime }))
|
|
||||||
chain.then(() => {
|
|
||||||
const idx = this.cruiseTours.findIndex(t => t.id === this.formId)
|
|
||||||
const presets = this.formPresets.map(p => ({
|
|
||||||
presetId: p.presetId,
|
|
||||||
dwellTime: p.dwellTime,
|
|
||||||
speed: p.speed
|
|
||||||
}))
|
|
||||||
const tour = { id: this.formId, name: this.formName, presets }
|
|
||||||
if (idx !== -1) {
|
|
||||||
this.$set(this.cruiseTours, idx, tour)
|
|
||||||
} else {
|
|
||||||
this.cruiseTours.push(tour)
|
|
||||||
}
|
|
||||||
this.cancelForm()
|
|
||||||
this.$message({ showClose: true, message: '保存成功', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error || '保存失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.submitting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearCruiseTours() {
|
|
||||||
if (this.cruiseTours.length === 0) return
|
|
||||||
this.$confirm('确定清空所有巡航组?', '提示', {
|
|
||||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.clearing = true
|
|
||||||
const cid = this.channelId
|
|
||||||
let chain = Promise.resolve()
|
|
||||||
this.cruiseTours.forEach(tour => {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('commonChanel/deletePointForCruise', { channelId: cid, tourId: tour.id, presetId: 0 }))
|
|
||||||
})
|
|
||||||
chain.then(() => {
|
|
||||||
this.cruiseTours = []
|
|
||||||
this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '清空成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '清空失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.clearing = false
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
startCruise(row) {
|
|
||||||
this.operatingId = row.id
|
|
||||||
this.$store.dispatch('commonChanel/startCruise', { channelId: this.channelId, tourId: row.id })
|
|
||||||
.then(() => {
|
|
||||||
this.cruisingCruiseId = row.id
|
|
||||||
this.$message({ showClose: true, message: '启用成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopCruise(row) {
|
|
||||||
this.operatingId = row.id
|
|
||||||
this.$store.dispatch('commonChanel/stopCruise', { channelId: this.channelId, tourId: row.id })
|
|
||||||
.then(() => {
|
|
||||||
this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '停止成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '停止失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteCruise(row) {
|
|
||||||
this.$confirm('确定删除此巡航组?', '提示', {
|
|
||||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.deletingId = row.id
|
|
||||||
this.$store.dispatch('commonChanel/deletePointForCruise', { channelId: this.channelId, tourId: row.id, presetId: 0 })
|
|
||||||
.then(() => {
|
|
||||||
const idx = this.cruiseTours.indexOf(row)
|
|
||||||
if (idx !== -1) this.cruiseTours.splice(idx, 1)
|
|
||||||
if (this.cruisingCruiseId === row.id) this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '删除成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '删除失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.deletingId = null
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzPreset" style="width: 100%">
|
|
||||||
<el-tag
|
|
||||||
v-for="item in presetList"
|
|
||||||
:key="item.presetId"
|
|
||||||
size="mini"
|
|
||||||
style="margin-right: 1rem; cursor: pointer; margin-bottom: 0.6rem"
|
|
||||||
@click="gotoPreset(item)"
|
|
||||||
>
|
|
||||||
{{ item.presetName || item.presetId }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzPreset',
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
presetList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getPresetList()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPresetList() {
|
|
||||||
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
|
|
||||||
.then(data => {
|
|
||||||
this.presetList = data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
gotoPreset(preset) {
|
|
||||||
this.$store.dispatch('commonChanel/callPreset', { channelId: this.channelId, presetId: preset.presetId })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '调用成功',
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :disabled="showAddForm" @click="openAdd">添加预置点</el-button>
|
|
||||||
<el-button :loading="clearing" :disabled="clearing" @click="clearAll">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle @click="getPresetList" />
|
|
||||||
</div>
|
|
||||||
<el-form v-if="showAddForm" size="small" inline style="margin-bottom: 6px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px; display: flex; align-items: center;">
|
|
||||||
<el-form-item label="序号" style="margin-bottom: 0; margin-right: 2rem">
|
|
||||||
<el-input-number v-model="addPresetId" :min="1" :max="255" controls-position="right" style="width: 180px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmAdd">确定</el-button>
|
|
||||||
<el-button @click="cancelAdd">取消</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-table
|
|
||||||
:data="presetList"
|
|
||||||
border
|
|
||||||
stripe
|
|
||||||
max-height="100%"
|
|
||||||
style="flex: 1"
|
|
||||||
>
|
|
||||||
<el-table-column prop="presetId" label="序号" align="center" />
|
|
||||||
<el-table-column label="名称">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<span>{{ row.presetName || ('预置点' + row.presetId) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" min-width="140" align="center">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button size="mini" type="text" @click="callPreset(row)">调用</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" :loading="deletingId === row.presetId" :disabled="deletingId !== null" @click="delPreset(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzPresetConfig',
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
presetList: [],
|
|
||||||
showAddForm: false,
|
|
||||||
addPresetId: 1,
|
|
||||||
submitting: false,
|
|
||||||
clearing: false,
|
|
||||||
deletingId: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getPresetList()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPresetList() {
|
|
||||||
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
|
|
||||||
.then(data => {
|
|
||||||
this.presetList = data || []
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openAdd() {
|
|
||||||
this.addPresetId = this.getNextAvailableId()
|
|
||||||
this.showAddForm = true
|
|
||||||
},
|
|
||||||
cancelAdd() {
|
|
||||||
this.showAddForm = false
|
|
||||||
this.addPresetId = 1
|
|
||||||
},
|
|
||||||
confirmAdd() {
|
|
||||||
const exists = this.presetList.some(p => p.presetId === this.addPresetId)
|
|
||||||
if (exists) {
|
|
||||||
this.$message({ showClose: true, message: '序号 ' + this.addPresetId + ' 已存在', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.submitting = true
|
|
||||||
this.$store.dispatch('commonChanel/addPreset', { channelId: this.channelId, presetId: this.addPresetId, presetName: '' })
|
|
||||||
.then(() => {
|
|
||||||
this.showAddForm = false
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getPresetList()
|
|
||||||
}, 600)
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.submitting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
callPreset(preset) {
|
|
||||||
this.$store.dispatch('commonChanel/callPreset', { channelId: this.channelId, presetId: preset.presetId })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '调用成功', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
delPreset(preset) {
|
|
||||||
this.$confirm('确定删除此预置位', '提示', {
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.deletingId = preset.presetId
|
|
||||||
this.$store.dispatch('commonChanel/deletePreset', { channelId: this.channelId, presetId: preset.presetId })
|
|
||||||
.then(() => {
|
|
||||||
this.getPresetList()
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.deletingId = null
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
clearAll() {
|
|
||||||
if (this.presetList.length === 0) return
|
|
||||||
this.$confirm('确定清空所有预置点?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.clearing = true
|
|
||||||
const promises = this.presetList.map(p =>
|
|
||||||
this.$store.dispatch('commonChanel/deletePreset', { channelId: this.channelId, presetId: p.presetId })
|
|
||||||
)
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
this.presetList = []
|
|
||||||
this.$message({ showClose: true, message: '清空成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '清空失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.clearing = false
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
getNextAvailableId() {
|
|
||||||
if (!this.presetList || this.presetList.length === 0) return 1
|
|
||||||
const used = this.presetList.map(p => Number(p.presetId)).sort((a, b) => a - b)
|
|
||||||
for (let i = 0; i < used.length - 1; i++) {
|
|
||||||
if (used[i + 1] - used[i] > 1) return used[i] + 1
|
|
||||||
}
|
|
||||||
return used[used.length - 1] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,177 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzScanConfig" style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :loading="adding" :disabled="adding" @click="addLineScan">添加线扫</el-button>
|
|
||||||
<el-button @click="clearAll">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle />
|
|
||||||
</div>
|
|
||||||
<div v-if="scanAreas.length > 0" style="flex: 1; overflow: auto;">
|
|
||||||
<el-table :data="scanAreas" max-height="100%" stripe border highlight-current-row height="100%">
|
|
||||||
<el-table-column label="序号" min-width="50">
|
|
||||||
<template v-slot="{ row }">{{ row.index }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="名称" min-width="80">
|
|
||||||
<template v-slot="{ row }">{{ row.name }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="左边界" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button type="text"
|
|
||||||
:style="{ color: row.leftBoundary ? '#67C23A' : '#409EFF' }"
|
|
||||||
:loading="boundaryLoading.index === row.index && boundaryLoading.side === 'Left'"
|
|
||||||
:disabled="operatingId !== null"
|
|
||||||
@click="setBoundary(row, 'Left')">
|
|
||||||
{{ row.leftBoundary ? '重新保存' : '待保存' }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="右边界" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button type="text"
|
|
||||||
:style="{ color: row.rightBoundary ? '#67C23A' : '#409EFF' }"
|
|
||||||
:loading="boundaryLoading.index === row.index && boundaryLoading.side === 'Right'"
|
|
||||||
:disabled="operatingId !== null"
|
|
||||||
@click="setBoundary(row, 'Right')">
|
|
||||||
{{ row.rightBoundary ? '重新保存' : '待保存' }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="速度" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.speed" :disabled="speedSaving === row.index" @change="onSpeedChange(row)">
|
|
||||||
<el-option v-for="s in 8" :key="s" :label="s" :value="s" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" min-width="120">
|
|
||||||
<template v-slot="{ row, $index }">
|
|
||||||
<el-button v-if="$index === cruisingScanIndex" type="text" style="color: #F56C6C" :loading="operatingId === row.index" :disabled="operatingId !== null" @click="stopScan(row)">停用</el-button>
|
|
||||||
<el-button v-else type="text" style="color: #409EFF" :disabled="operatingId !== null" :loading="operatingId === row.index" @click="startScan(row, $index)">启用</el-button>
|
|
||||||
<el-button type="text" style="color: #F56C6C" :disabled="operatingId !== null" @click="deleteScan(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else style="color: #909399; font-size: 12px; margin-bottom: 8px;">暂无线扫区域</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzScanConfig',
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
scanAreas: [],
|
|
||||||
cruisingScanIndex: null,
|
|
||||||
operatingId: null,
|
|
||||||
adding: false,
|
|
||||||
boundaryLoading: { index: null, side: null },
|
|
||||||
speedSaving: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getNextAvailableIndex() {
|
|
||||||
const used = new Set(this.scanAreas.filter(a => a.name && a.name.trim()).map(a => a.index))
|
|
||||||
for (let i = 0; i <= 255; i++) {
|
|
||||||
if (!used.has(i)) return i
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
addLineScan() {
|
|
||||||
const nextIndex = this.getNextAvailableIndex()
|
|
||||||
const name = '线扫' + nextIndex
|
|
||||||
this.adding = true
|
|
||||||
this.scanAreas.push({
|
|
||||||
index: nextIndex,
|
|
||||||
name: name,
|
|
||||||
leftBoundary: false,
|
|
||||||
rightBoundary: false,
|
|
||||||
speed: 5
|
|
||||||
})
|
|
||||||
this.$nextTick(() => { this.adding = false })
|
|
||||||
},
|
|
||||||
setBoundary(row, boundary) {
|
|
||||||
this.boundaryLoading = { index: row.index, side: boundary }
|
|
||||||
const action = boundary === 'Left' ? 'setLeftForScan' : 'setRightForScan'
|
|
||||||
this.$store.dispatch('commonChanel/' + action, { channelId: this.channelId, scanId: row.index })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: (boundary === 'Left' ? '左' : '右') + '边界设置成功', type: 'success' })
|
|
||||||
if (boundary === 'Left') {
|
|
||||||
row.leftBoundary = true
|
|
||||||
} else {
|
|
||||||
row.rightBoundary = true
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '边界设置失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.boundaryLoading = { index: null, side: null }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onSpeedChange(row) {
|
|
||||||
this.speedSaving = row.index
|
|
||||||
this.$store.dispatch('commonChanel/setSpeedForScan', { channelId: this.channelId, scanId: row.index, speed: row.speed })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '速度已保存', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '速度保存失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.speedSaving = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
startScan(row, index) {
|
|
||||||
this.operatingId = row.index
|
|
||||||
this.$store.dispatch('commonChanel/startScan', { channelId: this.channelId, scanId: row.index })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用成功', type: 'success' })
|
|
||||||
this.cruisingScanIndex = index
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopScan(row) {
|
|
||||||
this.operatingId = row.index
|
|
||||||
this.$store.dispatch('commonChanel/stopScan', { channelId: this.channelId, scanId: row.index })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '停用成功', type: 'success' })
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '停用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteScan(row) {
|
|
||||||
this.$confirm('确定删除线扫 ' + row.index + '?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
const idx = this.scanAreas.indexOf(row)
|
|
||||||
if (idx !== -1) this.scanAreas.splice(idx, 1)
|
|
||||||
if (this.cruisingScanIndex !== null && this.scanAreas[this.cruisingScanIndex] === undefined) {
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
}
|
|
||||||
this.$message({ showClose: true, message: '删除成功(仅本地列表,设备端配置需手动清除)', type: 'success' })
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
clearAll() {
|
|
||||||
if (this.scanAreas.length === 0) return
|
|
||||||
this.$confirm('确定清空所有线扫区域?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.scanAreas = []
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
this.$message({ showClose: true, message: '清空成功(仅本地列表,设备端配置需手动清除)', type: 'success' })
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-form inline label-width="120px" size="small">
|
|
||||||
<el-form-item label="开关编号" style="margin-bottom: 0;">
|
|
||||||
<el-input-number v-model="switchId" :min="1" :max="255" controls-position="right" style="width: 140px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="loading" :disabled="loading" @click="control('on')">开启</el-button>
|
|
||||||
<el-button :loading="loading" :disabled="loading" @click="control('off')">关闭</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
<el-divider />
|
|
||||||
|
|
||||||
<el-form-item style="margin-bottom: 0;" label="雨刷">
|
|
||||||
<el-button type="primary" :loading="wiperLoading" :disabled="wiperLoading" @click="wiperControl('on')">开启</el-button>
|
|
||||||
<el-button :loading="wiperLoading" :disabled="wiperLoading" @click="wiperControl('off')">关闭</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzSwitchConfig',
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
switchId: 1,
|
|
||||||
loading: false,
|
|
||||||
wiperLoading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
wiperControl(command) {
|
|
||||||
this.wiperLoading = true
|
|
||||||
this.$store.dispatch('commonChanel/wiper', { channelId: this.channelId, command })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: command === 'on' ? '雨刷已开启' : '雨刷已关闭', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.wiperLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
control(command) {
|
|
||||||
this.loading = true
|
|
||||||
this.$store.dispatch('commonChanel/auxiliary', { channelId: this.channelId, command, auxiliaryId: this.switchId })
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: command === 'on' ? '开关已开启' : '开关已关闭', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -91,12 +91,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="位置信息" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<span v-if="scope.row.gbLongitude && scope.row.gbLatitude">{{ scope.row.gbLongitude }}<br>{{ scope.row.gbLatitude }}</span>
|
|
||||||
<span v-if="!scope.row.gbLongitude || !scope.row.gbLatitude">无</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" min-width="100">
|
<el-table-column label="状态" min-width="100">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<div slot="reference" class="name-wrapper">
|
<div slot="reference" class="name-wrapper">
|
||||||
@ -126,7 +120,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import GroupTree from '../../common/GroupTree.vue'
|
import GroupTree from '../../common/GroupTree.vue'
|
||||||
import GbChannelSelect from '../../dialog/GbChannelSelect.vue'
|
import GbChannelSelect from '../../dialog/GbChannelSelect.vue'
|
||||||
import UnusualGroupChannelSelect from './UnusualGroupChannelSelect.vue'
|
import UnusualGroupChannelSelect from '../../dialog/UnusualGroupChannelSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Group',
|
name: 'Group',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="channelList" class="app-container" style="height: calc(100vh - 124px);">
|
<div id="channelList" class="app-container" style="height: calc(100vh - 124px);">
|
||||||
<div v-if="!editId && !showPtzConfig" style="height: 100%">
|
<div v-if="!editId" style="height: 100%">
|
||||||
<el-form :inline="true" size="mini">
|
<el-form :inline="true" size="mini">
|
||||||
<el-form-item label="搜索">
|
<el-form-item label="搜索">
|
||||||
<el-input
|
<el-input
|
||||||
@ -149,10 +149,6 @@
|
|||||||
设备录像</el-dropdown-item>
|
设备录像</el-dropdown-item>
|
||||||
<el-dropdown-item command="cloudRecords" :disabled="scope.row.gbStatus !== 'ON'">
|
<el-dropdown-item command="cloudRecords" :disabled="scope.row.gbStatus !== 'ON'">
|
||||||
云端录像</el-dropdown-item>
|
云端录像</el-dropdown-item>
|
||||||
<el-dropdown-item command="ptzConfig" :disabled="scope.row.gbStatus !== 'ON'">
|
|
||||||
云台配置</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="audioTalk" :disabled="scope.row.gbStatus !== 'ON'">
|
|
||||||
语音对讲</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
|
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@ -172,8 +168,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<devicePlayer ref="devicePlayer" />
|
<devicePlayer ref="devicePlayer" />
|
||||||
<audioTalk ref="audioTalk" />
|
|
||||||
<ptzConfig v-if="showPtzConfig" :channel-id="ptzConfigChannelId" @close="showPtzConfig = false" />
|
|
||||||
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
|
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
|
||||||
<chooseCivilCode ref="chooseCivilCode" />
|
<chooseCivilCode ref="chooseCivilCode" />
|
||||||
<chooseGroup ref="chooseGroup" />
|
<chooseGroup ref="chooseGroup" />
|
||||||
@ -182,20 +176,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import devicePlayer from './player.vue'
|
import devicePlayer from '@/views/common/channelPlayer/index.vue'
|
||||||
import audioTalk from './audioTalk.vue'
|
|
||||||
import ptzConfig from './ptzConfig.vue'
|
|
||||||
import Edit from './edit.vue'
|
import Edit from './edit.vue'
|
||||||
import ChooseCivilCode from '../dialog/chooseCivilCode.vue'
|
import ChooseCivilCode from '../dialog/chooseCivilCode.vue'
|
||||||
import ChooseGroup from '@/views/dialog/chooseGroup.vue'
|
import ChooseGroup from '@/views/dialog/chooseGroup.vue'
|
||||||
|
import { MessageBox } from 'element-ui'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChannelList',
|
name: 'ChannelList',
|
||||||
components: {
|
components: {
|
||||||
ChooseGroup,
|
ChooseGroup,
|
||||||
devicePlayer,
|
devicePlayer,
|
||||||
audioTalk,
|
|
||||||
ptzConfig,
|
|
||||||
ChooseCivilCode,
|
ChooseCivilCode,
|
||||||
ChannelEdit: Edit
|
ChannelEdit: Edit
|
||||||
},
|
},
|
||||||
@ -251,8 +243,6 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
beforeUrl: '/device',
|
beforeUrl: '/device',
|
||||||
editId: null,
|
editId: null,
|
||||||
showPtzConfig: false,
|
|
||||||
ptzConfigChannelId: null,
|
|
||||||
civilCodeName: null,
|
civilCodeName: null,
|
||||||
civilCodeDeviceId: null,
|
civilCodeDeviceId: null,
|
||||||
|
|
||||||
@ -403,11 +393,6 @@ export default {
|
|||||||
this.queryRecords(itemData)
|
this.queryRecords(itemData)
|
||||||
} else if (command === 'cloudRecords') {
|
} else if (command === 'cloudRecords') {
|
||||||
this.queryCloudRecords(itemData)
|
this.queryCloudRecords(itemData)
|
||||||
} else if (command === 'ptzConfig') {
|
|
||||||
this.ptzConfigChannelId = itemData.gbId
|
|
||||||
this.showPtzConfig = true
|
|
||||||
} else if (command === 'audioTalk') {
|
|
||||||
this.$refs.audioTalk.openDialog(itemData.gbId)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCheckIds: function() {
|
getCheckIds: function() {
|
||||||
|
|||||||
@ -1,158 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="devicePlayer" v-loading="isLoging">
|
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-if="showVideoDialog"
|
|
||||||
v-el-drag-dialog
|
|
||||||
title="视频播放"
|
|
||||||
top="10vh"
|
|
||||||
width="65vw"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:visible.sync="showVideoDialog"
|
|
||||||
@close="close()"
|
|
||||||
>
|
|
||||||
<div class="dhsdk-player-body">
|
|
||||||
|
|
||||||
<div class="player-side">
|
|
||||||
<div class="player-container" :style="{ height: playerHeight }">
|
|
||||||
<playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true"
|
|
||||||
@playerChanged="playerChanged" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-side">
|
|
||||||
<channelPtzPanel
|
|
||||||
:channel-id="channelId"
|
|
||||||
@drag-zoom-start="toggleDragZoom"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" class="control-tabs">
|
|
||||||
<el-tab-pane label="预置位" name="preset">
|
|
||||||
<channelPreset
|
|
||||||
v-if="tabActiveName === 'preset'"
|
|
||||||
:channel-id="channelId"
|
|
||||||
style="margin-top: 8px;"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="实时视频" name="media">
|
|
||||||
<streamMediaPanel v-if="tabActiveName === 'media'" :player-url="playerUrlInfo.playerUrl" :play-url="playerUrlInfo.playUrl" :stream-info="streamInfo" />
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="编码信息" name="codec">
|
|
||||||
<mediaInfo v-if="tabActiveName === 'codec'" ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" />
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import elDragDialog from '@/directive/el-drag-dialog'
|
|
||||||
import playerTabs from '../common/playerTabs.vue'
|
|
||||||
import channelPtzPanel from './common/channelPtzPanel.vue'
|
|
||||||
import channelPreset from './common/ptzPreset.vue'
|
|
||||||
import mediaInfo from '../common/mediaInfo.vue'
|
|
||||||
import streamMediaPanel from '../common/streamMediaPanel.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ChannelPlayer',
|
|
||||||
directives: { elDragDialog },
|
|
||||||
components: { playerTabs, channelPtzPanel, channelPreset, mediaInfo, streamMediaPanel },
|
|
||||||
props: {},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
videoUrl: '',
|
|
||||||
streamId: '',
|
|
||||||
app: '',
|
|
||||||
mediaServerId: '',
|
|
||||||
channelId: '',
|
|
||||||
tabActiveName: 'preset',
|
|
||||||
hasAudio: false,
|
|
||||||
isLoging: false,
|
|
||||||
showVideoDialog: false,
|
|
||||||
streamInfo: null,
|
|
||||||
playerHeight: '48vh',
|
|
||||||
playerUrlInfo: {
|
|
||||||
playerUrl: null,
|
|
||||||
playUrl: null
|
|
||||||
},
|
|
||||||
dragZoomDirection: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tabHandleClick(tab) {
|
|
||||||
if (tab.name === 'codec') {
|
|
||||||
this.$refs.mediaInfo && this.$refs.mediaInfo.startTask()
|
|
||||||
} else {
|
|
||||||
this.$refs.mediaInfo && this.$refs.mediaInfo.stopTask()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openDialog(tab, channelId, param) {
|
|
||||||
if (this.showVideoDialog) return
|
|
||||||
this.tabActiveName = tab || 'preset'
|
|
||||||
this.channelId = channelId
|
|
||||||
this.streamId = ''
|
|
||||||
this.mediaServerId = ''
|
|
||||||
this.app = ''
|
|
||||||
this.videoUrl = ''
|
|
||||||
if (param && param.streamInfo) {
|
|
||||||
this.play(param.streamInfo, param.hasAudio)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
play(streamInfo, hasAudio) {
|
|
||||||
this.streamInfo = streamInfo
|
|
||||||
this.hasAudio = hasAudio
|
|
||||||
this.isLoging = false
|
|
||||||
this.streamId = streamInfo.stream
|
|
||||||
this.app = streamInfo.app
|
|
||||||
this.mediaServerId = streamInfo.mediaServerId
|
|
||||||
this.showVideoDialog = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.setStreamInfo(streamInfo.transcodeStream || streamInfo)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
playerChanged(playerUrlInfo) {
|
|
||||||
this.playerUrlInfo = playerUrlInfo
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.stop()
|
|
||||||
}
|
|
||||||
this.videoUrl = ''
|
|
||||||
this.showVideoDialog = false
|
|
||||||
},
|
|
||||||
toggleDragZoom(direction) {
|
|
||||||
this.dragZoomDirection = direction
|
|
||||||
this.$refs.playerTabs.startDragZoom((params) => {
|
|
||||||
params.channelId = this.channelId
|
|
||||||
const action = this.dragZoomDirection === 'in' ? 'commonChanel/dragZoomIn' : 'commonChanel/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 = ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#devicePlayer .el-dialog__body { padding: 10px 20px; }
|
|
||||||
.dhsdk-player-body { display: flex; gap: 16px; }
|
|
||||||
.player-side { flex: 3; min-width: 0; }
|
|
||||||
.player-container { width: 100%; }
|
|
||||||
.control-side { flex: 2; min-width: 340px; display: flex; flex-direction: column; }
|
|
||||||
.control-tabs { flex: 1; display: flex; flex-direction: column; min-height: 220px }
|
|
||||||
.control-tabs .el-tabs__content { flex: 1; overflow: auto; }
|
|
||||||
.media-info-content { overflow: auto; }
|
|
||||||
.media-row { display: flex; margin-bottom: 0.5rem; height: 2.5rem; }
|
|
||||||
.media-label { width: 6rem; line-height: 2.5rem; text-align: right; flex-shrink: 0; }
|
|
||||||
</style>
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="dhPtzConfigPage">
|
|
||||||
<el-page-header content="云台设置" @back="$emit('close')" />
|
|
||||||
<div class="ptz-config-body">
|
|
||||||
<div class="config-sidebar">
|
|
||||||
<el-menu :default-active="activeTab" @select="handleMenuSelect">
|
|
||||||
<el-menu-item index="preset">
|
|
||||||
<i class="el-icon-map-location" style="margin-right: 6px" />
|
|
||||||
<span>预置点</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="cruise">
|
|
||||||
<i class="el-icon-s-order" style="margin-right: 6px" />
|
|
||||||
<span>巡航组</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="scan">
|
|
||||||
<i class="iconfont icon-slider-right" style="margin-right: 6px" />
|
|
||||||
<span>线性扫描</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="switch">
|
|
||||||
<i class="el-icon-s-tools" style="margin-right: 6px" />
|
|
||||||
<span>辅助开关</span>
|
|
||||||
</el-menu-item>
|
|
||||||
</el-menu>
|
|
||||||
</div>
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<div class="player-panel">
|
|
||||||
<playerPtzPanel :channel-id="channelId" />
|
|
||||||
</div>
|
|
||||||
<div class="tab-panel">
|
|
||||||
<ptzPresetConfig v-if="activeTab === 'preset'" :channel-id="channelId" />
|
|
||||||
<ptzCruiseConfig v-if="activeTab === 'cruise'" :channel-id="channelId" />
|
|
||||||
<ptzScanConfig v-if="activeTab === 'scan'" :channel-id="channelId" />
|
|
||||||
<ptzSwitchConfig v-if="activeTab === 'switch'" :channel-id="channelId" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import playerPtzPanel from './common/playerPtzPanel.vue'
|
|
||||||
import ptzPresetConfig from './common/ptzPresetConfig.vue'
|
|
||||||
import ptzCruiseConfig from './common/ptzCruiseConfig.vue'
|
|
||||||
import ptzScanConfig from './common/ptzScanConfig.vue'
|
|
||||||
import ptzSwitchConfig from './common/ptzSwitchConfig.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ChPtzConfig',
|
|
||||||
components: { playerPtzPanel, ptzPresetConfig, ptzCruiseConfig, ptzScanConfig, ptzSwitchConfig },
|
|
||||||
props: {
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeTab: 'preset'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleMenuSelect(index) {
|
|
||||||
this.activeTab = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#dhPtzConfigPage {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.ptz-config-body {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
.config-sidebar {
|
|
||||||
width: 140px;
|
|
||||||
flex: none;
|
|
||||||
border-right: 1px solid #e6e6e6;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.config-sidebar .el-menu {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
.content-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.player-panel {
|
|
||||||
width: 600px;
|
|
||||||
flex: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-right: 1px solid #e6e6e6;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
.tab-panel {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -90,12 +90,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="位置信息" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<span v-if="scope.row.gbLongitude && scope.row.gbLatitude">{{ scope.row.gbLongitude }}<br>{{ scope.row.gbLatitude }}</span>
|
|
||||||
<span v-if="!scope.row.gbLongitude || !scope.row.gbLatitude">无</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" min-width="100">
|
<el-table-column label="状态" min-width="100">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<div slot="reference" class="name-wrapper">
|
<div slot="reference" class="name-wrapper">
|
||||||
@ -124,7 +118,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import RegionTree from '../..//common/RegionTree.vue'
|
import RegionTree from '../..//common/RegionTree.vue'
|
||||||
import GbChannelSelect from '../../dialog/GbChannelSelect.vue'
|
import GbChannelSelect from '../../dialog/GbChannelSelect.vue'
|
||||||
import UnusualRegionChannelSelect from './UnusualRegionChannelSelect.vue'
|
import UnusualRegionChannelSelect from '../../dialog/UnusualRegionChannelSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Region',
|
name: 'Region',
|
||||||
|
|||||||
152
web/src/views/cloudRecord/player.vue → web/src/views/cloudRecord/cloudRecordPlayer.vue
Normal file → Executable file
152
web/src/views/cloudRecord/player.vue → web/src/views/cloudRecord/cloudRecordPlayer.vue
Normal file → Executable file
@ -1,14 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="cloudRecordPlayer" style="height: 100%">
|
<div id="cloudRecordPlayer" style="height: 100%">
|
||||||
<div class="cloud-record-playBox" :style="playBoxStyle">
|
<div class="cloud-record-playBox" :style="playBoxStyle">
|
||||||
<playerTabs
|
<jessibucaPlayer
|
||||||
|
v-if="playerType === 'Jessibuca'"
|
||||||
ref="recordVideoPlayer"
|
ref="recordVideoPlayer"
|
||||||
|
:height="'calc(100% - 250px)'"
|
||||||
:show-button="false"
|
:show-button="false"
|
||||||
:showTab="false"
|
|
||||||
@playTimeChange="showPlayTimeChange"
|
@playTimeChange="showPlayTimeChange"
|
||||||
@playStatusChange="playingChange"
|
@playStatusChange="playingChange"
|
||||||
@player-changed="onPlayerChanged"
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
/>
|
/>
|
||||||
|
<rtcPlayer
|
||||||
|
v-if="playerType === 'WebRTC'"
|
||||||
|
ref="recordVideoPlayer"
|
||||||
|
:has-audio="true"
|
||||||
|
:show-controls="false"
|
||||||
|
style="height: calc(100% - 250px)"
|
||||||
|
autoplay
|
||||||
|
@playTimeChange="showPlayTimeChange"
|
||||||
|
@playStatusChange="playingChange"
|
||||||
|
/>
|
||||||
|
<h265web v-if="playerType === 'H265web'" ref="recordVideoPlayer" :height="'calc(100% - 250px)'" :show-button="false" @playTimeChange="showPlayTimeChange" @playStatusChange="playingChange"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cloud-record-player-option-box">
|
<div class="cloud-record-player-option-box">
|
||||||
<div class="cloud-record-show-time">
|
<div class="cloud-record-show-time">
|
||||||
@ -33,6 +47,8 @@
|
|||||||
<div class="cloud-record-record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
<div class="cloud-record-record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
||||||
<a v-if="showListCallback" target="_blank" class="cloud-record-record-play-control-item iconfont icon-list" title="列表" @click="sidebarControl()" />
|
<a v-if="showListCallback" target="_blank" class="cloud-record-record-play-control-item iconfont icon-list" title="列表" @click="sidebarControl()" />
|
||||||
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-camera1196054easyiconnet" title="截图" @click="snap()" />
|
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-camera1196054easyiconnet" title="截图" @click="snap()" />
|
||||||
|
<!-- <a target="_blank" class="cloud-record-record-play-control-item iconfont icon-shuaxin11" title="刷新" @click="refresh()" />-->
|
||||||
|
<!-- <a target="_blank" class="cloud-record-record-play-control-item iconfont icon-xiazai011" title="下载" />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
@ -65,7 +81,9 @@
|
|||||||
<el-dropdown @command="changePlayerType" :popper-append-to-body='false' >
|
<el-dropdown @command="changePlayerType" :popper-append-to-body='false' >
|
||||||
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="选择播放器">{{ playerLabel }}</a>
|
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="选择播放器">{{ playerLabel }}</a>
|
||||||
<el-dropdown-menu slot="dropdown">
|
<el-dropdown-menu slot="dropdown">
|
||||||
<el-dropdown-item v-for="p in playerList" :key="p.key" :command="p.key">{{ p.label }}</el-dropdown-item>
|
<el-dropdown-item command="Jessibuca" >Jessibuca</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="WebRTC" >WebRTC</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="H265web" >H265web</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@ -79,7 +97,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import playerTabs from '../common/playerTabs.vue'
|
import h265web from '../common/h265web.vue'
|
||||||
|
import jessibucaPlayer from '@/views/common/jessibuca.vue'
|
||||||
|
import rtcPlayer from '../common/rtcPlayer.vue'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import momentDurationFormatSetup from 'moment-duration-format'
|
import momentDurationFormatSetup from 'moment-duration-format'
|
||||||
import screenfull from 'screenfull'
|
import screenfull from 'screenfull'
|
||||||
@ -88,11 +108,14 @@ momentDurationFormatSetup(moment)
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CloudRecordPlayer',
|
name: 'CloudRecordPlayer',
|
||||||
components: { playerTabs },
|
components: {
|
||||||
|
jessibucaPlayer, rtcPlayer, h265web
|
||||||
|
},
|
||||||
props: ['showListCallback', 'showNextCallback', 'showLastCallback', 'lastDiable', 'nextDiable'],
|
props: ['showListCallback', 'showNextCallback', 'showLastCallback', 'lastDiable', 'nextDiable'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showSidebar: false,
|
showSidebar: false,
|
||||||
|
videoUrl: null,
|
||||||
streamInfo: null,
|
streamInfo: null,
|
||||||
timeLen: null,
|
timeLen: null,
|
||||||
startTime: null,
|
startTime: null,
|
||||||
@ -105,8 +128,12 @@ export default {
|
|||||||
isFullScreen: false,
|
isFullScreen: false,
|
||||||
playing: false,
|
playing: false,
|
||||||
initTime: null,
|
initTime: null,
|
||||||
playerList: [],
|
playerType: 'Jessibuca',
|
||||||
playerLabel: 'Jessibuca',
|
playerUrls: {
|
||||||
|
Jessibuca: ['ws_flv', 'wss_flv'],
|
||||||
|
WebRTC: ['rtc', 'rtcs'],
|
||||||
|
H265web: ['ws_flv', 'wss_flv']
|
||||||
|
},
|
||||||
playSpeedRange: [1, 2, 4, 6, 8, 16, 20]
|
playSpeedRange: [1, 2, 4, 6, 8, 16, 20]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -132,7 +159,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
playTimeTotal() {
|
playTimeTotal() {
|
||||||
return { left: `calc(${this.playerTime/this.streamInfo.duration}*100% - 6px)` }
|
return { left: `calc(${this.playerTime/this.streamInfo.duration * 100}% - 6px)` }
|
||||||
},
|
},
|
||||||
playTimeTitleStyle() {
|
playTimeTitleStyle() {
|
||||||
return { left: (this.showTimeLeft - 16) + 'px' }
|
return { left: (this.showTimeLeft - 16) + 'px' }
|
||||||
@ -145,31 +172,21 @@ export default {
|
|||||||
}else {
|
}else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
playerLabel() {
|
||||||
|
const labels = { Jessibuca: 'Jessibuca', WebRTC: 'WebRTC', H265web: 'H265Web' }
|
||||||
|
return labels[this.playerType] || 'Jessibuca'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
document.addEventListener('mousemove', this.timeProcessMousemove)
|
document.addEventListener('mousemove', this.timeProcessMousemove)
|
||||||
document.addEventListener('mouseup', this.timeProcessMouseup)
|
document.addEventListener('mouseup', this.timeProcessMouseup)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {},
|
||||||
this.updatePlayerList()
|
|
||||||
},
|
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.$destroy('recordVideoPlayer')
|
this.$destroy('recordVideoPlayer')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updatePlayerList() {
|
|
||||||
if (this.$refs.recordVideoPlayer) {
|
|
||||||
this.playerList = this.$refs.recordVideoPlayer.getPlayerList()
|
|
||||||
const active = this.$refs.recordVideoPlayer.getActivePlayer()
|
|
||||||
const p = this.playerList.find(p => p.key === active)
|
|
||||||
this.playerLabel = p ? p.label : 'Jessibuca'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPlayerChanged(key) {
|
|
||||||
const p = this.playerList.find(p => p.key === key)
|
|
||||||
this.playerLabel = p ? p.label : 'Jessibuca'
|
|
||||||
},
|
|
||||||
timeProcessMouseup(event) {
|
timeProcessMouseup(event) {
|
||||||
this.isMousedown = false
|
this.isMousedown = false
|
||||||
},
|
},
|
||||||
@ -198,14 +215,11 @@ export default {
|
|||||||
this.showListCallback(this.showSidebar)
|
this.showListCallback(this.showSidebar)
|
||||||
},
|
},
|
||||||
snap() {
|
snap() {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
this.$refs.recordVideoPlayer.screenshot()
|
||||||
this.$refs.recordVideoPlayer.screenshot()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
this.$refs.recordVideoPlayer.destroy()
|
||||||
this.$refs.recordVideoPlayer.destroy()
|
this.$refs.recordVideoPlayer.playBtnClick()
|
||||||
}
|
|
||||||
},
|
},
|
||||||
playLast() {
|
playLast() {
|
||||||
this.showLastCallback()
|
this.showLastCallback()
|
||||||
@ -214,6 +228,7 @@ export default {
|
|||||||
this.showNextCallback()
|
this.showNextCallback()
|
||||||
},
|
},
|
||||||
changePlaySpeed(speed) {
|
changePlaySpeed(speed) {
|
||||||
|
// 倍速播放
|
||||||
this.playSpeed = speed
|
this.playSpeed = speed
|
||||||
this.$store.dispatch('cloudRecord/speed', {
|
this.$store.dispatch('cloudRecord/speed', {
|
||||||
mediaServerId: this.streamInfo.mediaServerId,
|
mediaServerId: this.streamInfo.mediaServerId,
|
||||||
@ -223,24 +238,32 @@ export default {
|
|||||||
speed: this.playSpeed,
|
speed: this.playSpeed,
|
||||||
schema: 'ts'
|
schema: 'ts'
|
||||||
})
|
})
|
||||||
if (this.$refs.recordVideoPlayer) {
|
this.$refs.recordVideoPlayer.setPlaybackRate(this.playSpeed)
|
||||||
this.$refs.recordVideoPlayer.setPlaybackRate(this.playSpeed)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
changePlayerType(playerType) {
|
changePlayerType(playerType) {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
if (this.playerType === playerType) {
|
||||||
this.$refs.recordVideoPlayer.switchPlayer(playerType)
|
return
|
||||||
const p = this.playerList.find(p => p.key === playerType)
|
}
|
||||||
this.playerLabel = p ? p.label : 'Jessibuca'
|
this.playerType = playerType
|
||||||
|
if (this.streamInfo) {
|
||||||
|
this.videoUrl = this.getUrlByStreamInfo()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.recordVideoPlayer) {
|
||||||
|
this.$refs.recordVideoPlayer.play(this.videoUrl)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
seekBackward() {
|
seekBackward() {
|
||||||
|
// 快退五秒
|
||||||
this.seekRecord(this.playerTime - 5 * 1000)
|
this.seekRecord(this.playerTime - 5 * 1000)
|
||||||
},
|
},
|
||||||
seekForward() {
|
seekForward() {
|
||||||
|
// 快进五秒
|
||||||
this.seekRecord(this.playerTime + 5 * 1000)
|
this.seekRecord(this.playerTime + 5 * 1000)
|
||||||
},
|
},
|
||||||
stopPLay() {
|
stopPLay() {
|
||||||
|
// 停止
|
||||||
if (this.$refs.recordVideoPlayer) {
|
if (this.$refs.recordVideoPlayer) {
|
||||||
this.$refs.recordVideoPlayer.destroy()
|
this.$refs.recordVideoPlayer.destroy()
|
||||||
}
|
}
|
||||||
@ -249,49 +272,60 @@ export default {
|
|||||||
this.playSpeed = 1
|
this.playSpeed = 1
|
||||||
},
|
},
|
||||||
pausePlay() {
|
pausePlay() {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
// 暂停
|
||||||
this.$refs.recordVideoPlayer.pause()
|
this.$refs.recordVideoPlayer.pause()
|
||||||
}
|
// TODO
|
||||||
},
|
},
|
||||||
play() {
|
play() {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
if (this.$refs.recordVideoPlayer.loaded) {
|
||||||
if (this.$refs.recordVideoPlayer.$refs[this.$refs.recordVideoPlayer.getActivePlayer()] &&
|
this.$refs.recordVideoPlayer.unPause()
|
||||||
this.$refs.recordVideoPlayer.$refs[this.$refs.recordVideoPlayer.getActivePlayer()].loaded) {
|
} else {
|
||||||
this.$refs.recordVideoPlayer.$refs[this.$refs.recordVideoPlayer.getActivePlayer()].unPause()
|
this.playRecord()
|
||||||
} else {
|
|
||||||
this.playRecord()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fullScreen() {
|
fullScreen() {
|
||||||
|
// 全屏
|
||||||
if (this.isFullScreen) {
|
if (this.isFullScreen) {
|
||||||
screenfull.exit()
|
screenfull.exit()
|
||||||
this.isFullScreen = false
|
this.isFullScreen = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const playerWrapper = this.$refs.recordVideoPlayer ? this.$refs.recordVideoPlayer.$refs.playerWrapper : null
|
const playerWidth = this.$refs.recordVideoPlayer.playerWidth
|
||||||
const playerWidth = playerWrapper ? playerWrapper.clientWidth : 0
|
const playerHeight = this.$refs.recordVideoPlayer.playerHeight
|
||||||
const playerHeight = playerWrapper ? playerWrapper.clientHeight : 0
|
|
||||||
screenfull.request(document.getElementById('cloudRecordPlayer'))
|
screenfull.request(document.getElementById('cloudRecordPlayer'))
|
||||||
screenfull.on('change', (event) => {
|
screenfull.on('change', (event) => {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
this.$refs.recordVideoPlayer.resize(playerWidth, playerHeight)
|
||||||
this.$refs.recordVideoPlayer.resize(playerWidth, playerHeight)
|
|
||||||
}
|
|
||||||
this.isFullScreen = screenfull.isFullscreen
|
this.isFullScreen = screenfull.isFullscreen
|
||||||
})
|
})
|
||||||
this.isFullScreen = true
|
this.isFullScreen = true
|
||||||
},
|
},
|
||||||
setStreamInfo(streamInfo, timeLen, startTime) {
|
setStreamInfo(streamInfo, timeLen, startTime) {
|
||||||
|
const keys = this.playerUrls[this.playerType]
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
this.videoUrl = streamInfo[keys[1]]
|
||||||
|
} else {
|
||||||
|
this.videoUrl = streamInfo[keys[0]]
|
||||||
|
}
|
||||||
|
console.log(location.protocol)
|
||||||
this.streamInfo = streamInfo
|
this.streamInfo = streamInfo
|
||||||
this.timeLen = timeLen
|
this.timeLen = timeLen
|
||||||
this.startTime = startTime
|
this.startTime = startTime
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.recordVideoPlayer) {
|
if (this.$refs.recordVideoPlayer) {
|
||||||
console.log(streamInfo)
|
this.$refs.recordVideoPlayer.play(this.videoUrl)
|
||||||
this.$refs.recordVideoPlayer.setStreamInfo(streamInfo)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getUrlByStreamInfo() {
|
||||||
|
if (!this.streamInfo) return ''
|
||||||
|
const keys = this.playerUrls[this.playerType]
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
this.videoUrl = this.streamInfo[keys[1]]
|
||||||
|
} else {
|
||||||
|
this.videoUrl = this.streamInfo[keys[0]]
|
||||||
|
}
|
||||||
|
return this.videoUrl
|
||||||
|
},
|
||||||
seekRecord(playSeekValue, callback) {
|
seekRecord(playSeekValue, callback) {
|
||||||
this.$store.dispatch('cloudRecord/seek', {
|
this.$store.dispatch('cloudRecord/seek', {
|
||||||
mediaServerId: this.streamInfo.mediaServerId,
|
mediaServerId: this.streamInfo.mediaServerId,
|
||||||
@ -406,10 +440,10 @@ export default {
|
|||||||
color: rgb(217, 217, 217);
|
color: rgb(217, 217, 217);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-shadow:
|
text-shadow:
|
||||||
-1px -1px 0 black,
|
-1px -1px 0 black, /* 左上角阴影 */
|
||||||
1px -1px 0 black,
|
1px -1px 0 black, /* 右上角阴影 */
|
||||||
-1px 1px 0 black,
|
-1px 1px 0 black, /* 左下角阴影 */
|
||||||
1px 1px 0 black;
|
1px 1px 0 black; /* 右下角阴影 */
|
||||||
}
|
}
|
||||||
.record-play-control-player {
|
.record-play-control-player {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
@ -58,7 +58,7 @@
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import momentDurationFormatSetup from 'moment-duration-format'
|
import momentDurationFormatSetup from 'moment-duration-format'
|
||||||
import screenfull from 'screenfull'
|
import screenfull from 'screenfull'
|
||||||
import cloudRecordPlayer from './player.vue'
|
import cloudRecordPlayer from './cloudRecordPlayer.vue'
|
||||||
|
|
||||||
momentDurationFormatSetup(moment)
|
momentDurationFormatSetup(moment)
|
||||||
|
|
||||||
@ -284,15 +284,12 @@ export default {
|
|||||||
this.$refs.cloudRecordPlayer.setStreamInfo(data, this.detailFiles[this.chooseFileIndex].timeLen, this.detailFiles[this.chooseFileIndex].startTime)
|
this.$refs.cloudRecordPlayer.setStreamInfo(data, this.detailFiles[this.chooseFileIndex].timeLen, this.detailFiles[this.chooseFileIndex].startTime)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$message({
|
console.log(error)
|
||||||
showClose: true,
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.playLoading = false
|
this.playLoading = false
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
downloadFile(file) {
|
downloadFile(file) {
|
||||||
this.$store.dispatch('cloudRecord/getPlayPath', file.id)
|
this.$store.dispatch('cloudRecord/getPlayPath', file.id)
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
import elDragDialog from '@/directive/el-drag-dialog'
|
import elDragDialog from '@/directive/el-drag-dialog'
|
||||||
import cloudRecordPlayer from './player.vue'
|
import cloudRecordPlayer from './cloudRecordPlayer.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PlayerDialog',
|
name: 'PlayerDialog',
|
||||||
|
|||||||
0
web/src/views/common/channelPlayer/chooseChannelForJt.vue
Executable file
0
web/src/views/common/channelPlayer/chooseChannelForJt.vue
Executable file
910
web/src/views/common/channelPlayer/index.vue
Executable file
910
web/src/views/common/channelPlayer/index.vue
Executable file
@ -0,0 +1,910 @@
|
|||||||
|
<template>
|
||||||
|
<div id="devicePlayer" v-loading="isLoging">
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-if="showVideoDialog"
|
||||||
|
v-el-drag-dialog
|
||||||
|
title="视频播放"
|
||||||
|
top="0"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
@close="close()"
|
||||||
|
>
|
||||||
|
<div style="width: 100%; height: 100%">
|
||||||
|
<el-tabs
|
||||||
|
v-if="Object.keys(this.player).length > 1"
|
||||||
|
v-model="activePlayer"
|
||||||
|
type="card"
|
||||||
|
:stretch="true"
|
||||||
|
@tab-click="changePlayer"
|
||||||
|
>
|
||||||
|
<el-tab-pane label="Jessibuca" name="jessibuca">
|
||||||
|
<jessibucaPlayer
|
||||||
|
v-if="activePlayer === 'jessibuca'"
|
||||||
|
ref="jessibuca"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="WebRTC" name="webRTC">
|
||||||
|
<rtc-player
|
||||||
|
v-if="activePlayer === 'webRTC'"
|
||||||
|
ref="webRTC"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
height="100px"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="h265web" name="h265web">
|
||||||
|
<h265web
|
||||||
|
v-if="activePlayer === 'h265web'"
|
||||||
|
ref="h265web"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
:show-button="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<jessibucaPlayer
|
||||||
|
v-if="Object.keys(this.player).length == 1 && this.player.jessibuca"
|
||||||
|
ref="jessibuca"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
/>
|
||||||
|
<rtc-player
|
||||||
|
v-if="Object.keys(this.player).length == 1 && this.player.webRTC"
|
||||||
|
ref="rtcPlayer"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
height="100px"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
/>
|
||||||
|
<h265web
|
||||||
|
v-if="Object.keys(this.player).length == 1 && this.player.h265web"
|
||||||
|
ref="h265web"
|
||||||
|
:visible.sync="showVideoDialog"
|
||||||
|
:error="videoError"
|
||||||
|
:message="videoError"
|
||||||
|
height="100px"
|
||||||
|
:has-audio="hasAudio"
|
||||||
|
fluent
|
||||||
|
autoplay
|
||||||
|
live
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="shared" style="text-align: right; margin-top: 1rem;">
|
||||||
|
|
||||||
|
<el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
|
||||||
|
<el-tab-pane label="实时视频" name="media">
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
|
||||||
|
<template slot="append">
|
||||||
|
<i
|
||||||
|
class="cpoy-btn el-icon-document-copy"
|
||||||
|
title="点击拷贝"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click="copyUrl(getPlayerShared.sharedUrl)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
|
||||||
|
<template slot="append">
|
||||||
|
<i
|
||||||
|
class="cpoy-btn el-icon-document-copy"
|
||||||
|
title="点击拷贝"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click="copyUrl(getPlayerShared.sharedIframe)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true">
|
||||||
|
<el-button
|
||||||
|
slot="append"
|
||||||
|
icon="el-icon-document-copy"
|
||||||
|
title="点击拷贝"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click="copyUrl(getPlayerShared.sharedIframe)"
|
||||||
|
/>
|
||||||
|
<el-dropdown v-if="streamInfo" slot="prepend" trigger="click" @command="copyUrl">
|
||||||
|
<el-button>
|
||||||
|
更多地址<i class="el-icon-arrow-down el-icon--right" />
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-if="streamInfo.flv" :command="streamInfo.flv">
|
||||||
|
<el-tag>FLV:</el-tag>
|
||||||
|
<span>{{ streamInfo.flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_flv" :command="streamInfo.https_flv">
|
||||||
|
<el-tag>FLV(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_flv" :command="streamInfo.ws_flv">
|
||||||
|
<el-tag>FLV(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_flv" :command="streamInfo.wss_flv">
|
||||||
|
<el-tag>FLV(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.fmp4" :command="streamInfo.fmp4">
|
||||||
|
<el-tag>FMP4:</el-tag>
|
||||||
|
<span>{{ streamInfo.fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_fmp4" :command="streamInfo.https_fmp4">
|
||||||
|
<el-tag>FMP4(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_fmp4" :command="streamInfo.ws_fmp4">
|
||||||
|
<el-tag>FMP4(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_fmp4" :command="streamInfo.wss_fmp4">
|
||||||
|
<el-tag>FMP4(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.hls" :command="streamInfo.hls">
|
||||||
|
<el-tag>HLS:</el-tag>
|
||||||
|
<span>{{ streamInfo.hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_hls" :command="streamInfo.https_hls">
|
||||||
|
<el-tag>HLS(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_hls" :command="streamInfo.ws_hls">
|
||||||
|
<el-tag>HLS(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_hls" :command="streamInfo.wss_hls">
|
||||||
|
<el-tag>HLS(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ts" :command="streamInfo.ts">
|
||||||
|
<el-tag>TS:</el-tag>
|
||||||
|
<span>{{ streamInfo.ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_ts" :command="streamInfo.https_ts">
|
||||||
|
<el-tag>TS(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_ts" :command="streamInfo.ws_ts">
|
||||||
|
<el-tag>TS(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_ts" :command="streamInfo.wss_ts">
|
||||||
|
<el-tag>TS(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtc" :command="streamInfo.rtc">
|
||||||
|
<el-tag>RTC:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtc }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtcs" :command="streamInfo.rtcs">
|
||||||
|
<el-tag>RTCS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtcs }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtmp" :command="streamInfo.rtmp">
|
||||||
|
<el-tag>RTMP:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtmp }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtmps" :command="streamInfo.rtmps">
|
||||||
|
<el-tag>RTMPS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtmps }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtsp" :command="streamInfo.rtsp">
|
||||||
|
<el-tag>RTSP:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtsp }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtsps" :command="streamInfo.rtsps">
|
||||||
|
<el-tag>RTSPS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtsps }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
|
||||||
|
<!--遥控界面-->
|
||||||
|
<el-tab-pane v-if="showPtz" label="云台控制" name="control">
|
||||||
|
<div style="display: grid; grid-template-columns: 240px auto; height: 180px; overflow: auto">
|
||||||
|
<div style="display: grid; grid-template-columns: 6.25rem auto;">
|
||||||
|
|
||||||
|
<div class="control-wrapper">
|
||||||
|
<div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-top" />
|
||||||
|
<div class="control-inner-btn control-inner" />
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-left" @mousedown="ptzCamera('left')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-left" />
|
||||||
|
<div class="control-inner-btn control-inner" />
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-bottom" @mousedown="ptzCamera('down')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-bottom" />
|
||||||
|
<div class="control-inner-btn control-inner" />
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-right" @mousedown="ptzCamera('right')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-right" />
|
||||||
|
<div class="control-inner-btn control-inner" />
|
||||||
|
</div>
|
||||||
|
<div class="control-round">
|
||||||
|
<div class="control-round-inner"><i class="fa fa-pause-circle" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 6.25rem;">
|
||||||
|
<el-slider v-model="controSpeed" :max="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div style="" title="变倍+" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
<div style="" title="变倍-" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-zoom-out control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div title="聚焦+" @mousedown="focusCamera('near')" @mouseup="focusCamera('stop')">
|
||||||
|
<i class="iconfont icon-bianjiao-fangda control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
<div title="聚焦-" @mousedown="focusCamera('far')" @mouseup="focusCamera('stop')">
|
||||||
|
<i class="iconfont icon-bianjiao-suoxiao control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div title="光圈+" @mousedown="irisCamera('in')" @mouseup="irisCamera('stop')">
|
||||||
|
<i class="iconfont icon-guangquan control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
<div title="光圈-" @mousedown="irisCamera('out')" @mouseup="irisCamera('stop')">
|
||||||
|
<i class="iconfont icon-guangquan- control-zoom-btn" style="font-size: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: left" v-if="tabActiveName === 'control'">
|
||||||
|
<el-select
|
||||||
|
v-model="ptzMethod"
|
||||||
|
style="width: 100%"
|
||||||
|
size="mini"
|
||||||
|
placeholder="请选择云台功能"
|
||||||
|
>
|
||||||
|
<el-option label="预置点" value="preset" />
|
||||||
|
<el-option label="巡航组" value="cruise" />
|
||||||
|
<el-option label="自动扫描" value="scan" />
|
||||||
|
<el-option label="雨刷" value="wiper" />
|
||||||
|
<el-option label="辅助开关" value="switch" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<ptzPreset v-if="ptzMethod === 'preset'" :channel-id="channelId" style="margin-top: 1rem" />
|
||||||
|
<ptzCruising v-if="ptzMethod === 'cruise'" :channel-id="channelId" style="margin-top: 1rem" />
|
||||||
|
<ptzScan v-if="ptzMethod === 'scan'" :channel-id="channelId" style="margin-top: 1rem" />
|
||||||
|
<ptzWiper v-if="ptzMethod === 'wiper'" :channel-id="channelId" style="margin-top: 1rem" />
|
||||||
|
<ptzSwitch v-if="ptzMethod === 'switch'" :channel-id="channelId" style="margin-top: 1rem" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="编码信息" name="codec">
|
||||||
|
<mediaInfo ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane v-if="showBroadcast" label="语音对讲" name="broadcast">
|
||||||
|
<div style="padding: 0 10px">
|
||||||
|
<!-- <el-switch v-model="broadcastMode" :disabled="broadcastStatus !== -1" active-color="#409EFF"-->
|
||||||
|
<!-- active-text="喊话(Broadcast)"-->
|
||||||
|
<!-- inactive-text="对讲(Talk)"></el-switch>-->
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import elDragDialog from '@/directive/el-drag-dialog'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import rtcPlayer from '../rtcPlayer.vue'
|
||||||
|
import jessibucaPlayer from '../jessibuca.vue'
|
||||||
|
import PtzPreset from './ptzPreset.vue'
|
||||||
|
import PtzCruising from './ptzCruising.vue'
|
||||||
|
import ptzScan from './ptzScan.vue'
|
||||||
|
import ptzWiper from './ptzWiper.vue'
|
||||||
|
import ptzSwitch from './ptzSwitch.vue'
|
||||||
|
import mediaInfo from '../mediaInfo.vue'
|
||||||
|
import H265web from '../h265web.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DevicePlayer',
|
||||||
|
directives: { elDragDialog },
|
||||||
|
components: {
|
||||||
|
H265web,
|
||||||
|
PtzPreset, PtzCruising, ptzScan, ptzWiper, ptzSwitch, mediaInfo,
|
||||||
|
jessibucaPlayer, rtcPlayer
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
|
||||||
|
videoUrl: '',
|
||||||
|
activePlayer: 'jessibuca',
|
||||||
|
// 如何你只是用一种播放器,直接注释掉不用的部分即可
|
||||||
|
player: {
|
||||||
|
jessibuca: ['ws_flv', 'wss_flv'],
|
||||||
|
webRTC: ['rtc', 'rtcs'],
|
||||||
|
h265web: ['ws_flv', 'wss_flv']
|
||||||
|
},
|
||||||
|
showVideoDialog: false,
|
||||||
|
channelId: null,
|
||||||
|
streamId: '',
|
||||||
|
ptzMethod: 'preset',
|
||||||
|
ptzPresetId: '',
|
||||||
|
app: '',
|
||||||
|
mediaServerId: '',
|
||||||
|
tabActiveName: 'media',
|
||||||
|
hasAudio: false,
|
||||||
|
loadingRecords: false,
|
||||||
|
recordsLoading: false,
|
||||||
|
isLoging: false,
|
||||||
|
controSpeed: 30,
|
||||||
|
timeVal: 0,
|
||||||
|
timeMin: 0,
|
||||||
|
timeMax: 1440,
|
||||||
|
presetPos: 1,
|
||||||
|
cruisingSpeed: 100,
|
||||||
|
cruisingTime: 5,
|
||||||
|
cruisingGroup: 0,
|
||||||
|
scanSpeed: 100,
|
||||||
|
scanGroup: 0,
|
||||||
|
tracks: [],
|
||||||
|
showPtz: true,
|
||||||
|
showBroadcast: true,
|
||||||
|
showRrecord: true,
|
||||||
|
sliderTime: 0,
|
||||||
|
seekTime: 0,
|
||||||
|
recordStartTime: 0,
|
||||||
|
showTimeText: '00:00:00',
|
||||||
|
streamInfo: null,
|
||||||
|
broadcastMode: true,
|
||||||
|
broadcastRtc: null,
|
||||||
|
broadcastStatus: -1 // -2 正在释放资源 -1 默认状态 0 等待接通 1 接通成功
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getPlayerShared: function() {
|
||||||
|
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
|
||||||
|
const type = typeMap[this.activePlayer] || 0
|
||||||
|
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
|
||||||
|
return {
|
||||||
|
sharedUrl: baseUrl,
|
||||||
|
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
|
||||||
|
sharedRtmp: this.videoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
if (Object.keys(this.player).length === 1) {
|
||||||
|
this.activePlayer = Object.keys(this.player)[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tabHandleClick: function(tab, event) {
|
||||||
|
console.log(tab)
|
||||||
|
this.tracks = []
|
||||||
|
if (tab.name === 'codec') {
|
||||||
|
this.$refs.mediaInfo.startTask()
|
||||||
|
} else {
|
||||||
|
this.$refs.mediaInfo.stopTask()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changePlayer: function(tab) {
|
||||||
|
console.log(this.player[tab.name][0])
|
||||||
|
this.activePlayer = tab.name
|
||||||
|
this.videoUrl = this.getUrlByStreamInfo()
|
||||||
|
console.log(this.videoUrl)
|
||||||
|
},
|
||||||
|
openDialog: function(tab, channelId, param) {
|
||||||
|
if (this.showVideoDialog) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.tabActiveName = tab
|
||||||
|
this.channelId = channelId
|
||||||
|
this.streamId = ''
|
||||||
|
this.mediaServerId = ''
|
||||||
|
this.app = ''
|
||||||
|
this.videoUrl = ''
|
||||||
|
if (this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].pause()
|
||||||
|
}
|
||||||
|
switch (tab) {
|
||||||
|
case 'media':
|
||||||
|
this.play(param.streamInfo, param.hasAudio)
|
||||||
|
break
|
||||||
|
case 'streamPlay':
|
||||||
|
this.tabActiveName = 'media'
|
||||||
|
this.showRrecord = false
|
||||||
|
this.showPtz = false
|
||||||
|
this.showBroadcast = false
|
||||||
|
this.play(param.streamInfo, param.hasAudio)
|
||||||
|
break
|
||||||
|
case 'control':
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
play: function(streamInfo, hasAudio) {
|
||||||
|
this.streamInfo = streamInfo
|
||||||
|
this.hasAudio = hasAudio
|
||||||
|
this.isLoging = false
|
||||||
|
// this.videoUrl = streamInfo.rtc;
|
||||||
|
this.videoUrl = this.getUrlByStreamInfo()
|
||||||
|
this.streamId = streamInfo.stream
|
||||||
|
this.app = streamInfo.app
|
||||||
|
this.mediaServerId = streamInfo.mediaServerId
|
||||||
|
this.playFromStreamInfo(false, streamInfo)
|
||||||
|
},
|
||||||
|
getUrlByStreamInfo() {
|
||||||
|
console.log(this.streamInfo)
|
||||||
|
let streamInfo = this.streamInfo
|
||||||
|
if (this.streamInfo.transcodeStream) {
|
||||||
|
streamInfo = this.streamInfo.transcodeStream
|
||||||
|
}
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
this.videoUrl = streamInfo[this.player[this.activePlayer][1]]
|
||||||
|
} else {
|
||||||
|
this.videoUrl = streamInfo[this.player[this.activePlayer][0]]
|
||||||
|
}
|
||||||
|
return this.videoUrl
|
||||||
|
},
|
||||||
|
|
||||||
|
playFromStreamInfo: function(realHasAudio, streamInfo) {
|
||||||
|
this.showVideoDialog = true
|
||||||
|
this.hasaudio = realHasAudio && this.hasaudio
|
||||||
|
if (this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function() {
|
||||||
|
console.log('关闭视频')
|
||||||
|
if (this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].pause()
|
||||||
|
}
|
||||||
|
this.videoUrl = ''
|
||||||
|
this.showVideoDialog = false
|
||||||
|
this.stopBroadcast()
|
||||||
|
},
|
||||||
|
ptzCamera: function(command) {
|
||||||
|
console.log('云台控制:' + command)
|
||||||
|
this.$store.dispatch('commonChanel/ptz',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
panSpeed: this.controSpeed,
|
||||||
|
tiltSpeed: this.controSpeed,
|
||||||
|
zoomSpeed: this.controSpeed
|
||||||
|
})
|
||||||
|
},
|
||||||
|
irisCamera: function(command) {
|
||||||
|
this.$store.dispatch('commonChanel/iris',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
speed: this.controSpeed
|
||||||
|
})
|
||||||
|
},
|
||||||
|
focusCamera: function(command) {
|
||||||
|
this.$store.dispatch('commonChanel/focus',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
speed: this.controSpeed
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// ////////////////////播放器事件处理//////////////////////////
|
||||||
|
videoError: function(e) {
|
||||||
|
console.log('播放器错误:' + JSON.stringify(e))
|
||||||
|
},
|
||||||
|
copyUrl: function(dropdownItem) {
|
||||||
|
console.log(dropdownItem)
|
||||||
|
this.$copyText(dropdownItem).then((e) => {
|
||||||
|
this.$message.success({
|
||||||
|
showClose: true,
|
||||||
|
message: '成功拷贝到粘贴板'
|
||||||
|
})
|
||||||
|
}, (e) => {
|
||||||
|
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getBroadcastStatus() {
|
||||||
|
if (this.broadcastStatus == -2) {
|
||||||
|
return 'primary'
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus == -1) {
|
||||||
|
return 'primary'
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus == 0) {
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus === 1) {
|
||||||
|
return 'danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
broadcastStatusClick() {
|
||||||
|
if (this.broadcastStatus === -1) {
|
||||||
|
// 默认状态, 开始
|
||||||
|
this.broadcastStatus = 0
|
||||||
|
// 发起语音对讲
|
||||||
|
this.$store.dispatch('play/broadcastStart', [ this.channelId, this.broadcastMode])
|
||||||
|
.then(data => {
|
||||||
|
const streamInfo = data.streamInfo
|
||||||
|
if (document.location.protocol.includes('https')) {
|
||||||
|
this.startBroadcast(streamInfo.rtcs)
|
||||||
|
} else {
|
||||||
|
this.startBroadcast(streamInfo.rtc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (this.broadcastStatus === 1) {
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
this.broadcastRtc.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startBroadcast(url) {
|
||||||
|
// 获取推流鉴权Key
|
||||||
|
this.$store.dispatch('user/getUserInfo')
|
||||||
|
.then((data) => {
|
||||||
|
if (data == null) {
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pushKey = data.pushKey
|
||||||
|
// 获取推流鉴权KEY
|
||||||
|
url += '&sign=' + crypto.createHash('md5').update(pushKey, 'utf8').digest('hex')
|
||||||
|
console.log('开始语音喊话: ' + url)
|
||||||
|
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, (e) => { // 获取到了本地流
|
||||||
|
console.error('不支持webrtc', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '不支持webrtc, 无法进行语音喊话',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
})
|
||||||
|
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => { // ICE 协商出错
|
||||||
|
console.error('ICE 协商出错')
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'ICE 协商出错',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
})
|
||||||
|
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => { // offer anwser 交换失败
|
||||||
|
console.error('offer anwser 交换失败', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'offer anwser 交换失败' + e,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
})
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => { // offer anwser 交换失败
|
||||||
|
console.log('状态改变', 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, (e) => { // offer anwser 交换失败
|
||||||
|
console.log('捕获流失败', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '捕获流失败' + e,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
})
|
||||||
|
}).catch(e => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: e,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stopBroadcast() {
|
||||||
|
this.broadcastRtc.close()
|
||||||
|
this.broadcastStatus = -1
|
||||||
|
this.$store.dispatch('play/broadcastStop', [this.channelId])
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.control-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 6.25rem;
|
||||||
|
height: 6.25rem;
|
||||||
|
max-width: 6.25rem;
|
||||||
|
max-height: 6.25rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 5rem;
|
||||||
|
height: 11rem;
|
||||||
|
max-height: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
width: 44%;
|
||||||
|
height: 44%;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #78aee4;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #78aee4;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn i:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-zoom-btn:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-round {
|
||||||
|
position: absolute;
|
||||||
|
top: 21%;
|
||||||
|
left: 21%;
|
||||||
|
width: 58%;
|
||||||
|
height: 58%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-round-inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 13%;
|
||||||
|
top: 13%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
font-size: 40px;
|
||||||
|
color: #78aee4;
|
||||||
|
border: 1px solid #78aee4;
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-inner-btn {
|
||||||
|
position: absolute;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top {
|
||||||
|
top: -8%;
|
||||||
|
left: 27%;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top i {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top .control-inner {
|
||||||
|
left: -1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 100% 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top .fa {
|
||||||
|
transform: rotate(45deg) translateY(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left {
|
||||||
|
top: 27%;
|
||||||
|
left: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 0 5px 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left .control-inner {
|
||||||
|
right: -1px;
|
||||||
|
top: -1px;
|
||||||
|
border-bottom: 1px solid #78aee4;
|
||||||
|
border-left: 1px solid #78aee4;
|
||||||
|
border-radius: 0 0 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left .fa {
|
||||||
|
transform: rotate(-45deg) translateX(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right {
|
||||||
|
top: 27%;
|
||||||
|
right: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right .control-inner {
|
||||||
|
left: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
border-top: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 100% 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right .fa {
|
||||||
|
transform: rotate(-45deg) translateX(7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom {
|
||||||
|
left: 27%;
|
||||||
|
bottom: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 0 5px 100% 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom .control-inner {
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
border-bottom: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 0 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom .fa {
|
||||||
|
transform: rotate(-45deg) translateY(7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trank {
|
||||||
|
width: 80%;
|
||||||
|
height: 180px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 10%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trankInfo {
|
||||||
|
width: 80%;
|
||||||
|
padding: 0 10%;
|
||||||
|
}
|
||||||
|
.el-dialog__body{
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.ptz-btn-box {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
web/src/views/common/channelPlayer/jtDeviceEdit.vue
Executable file
88
web/src/views/common/channelPlayer/jtDeviceEdit.vue
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div id="deviceEdit" v-loading="isLoging">
|
||||||
|
<el-dialog
|
||||||
|
title="设备编辑"
|
||||||
|
width="40%"
|
||||||
|
top="2rem"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:visible.sync="showDialog"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="close()"
|
||||||
|
>
|
||||||
|
<div id="shared" style="margin-top: 1rem;margin-right: 100px;">
|
||||||
|
<el-form ref="form" :rules="rules" :model="form" label-width="200px" >
|
||||||
|
<el-form-item label="终端手机号" prop="phoneNumber">
|
||||||
|
<el-input v-model="form.phoneNumber" clearable></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<div style="float: right;">
|
||||||
|
<el-button type="primary" @click="onSubmit" >确认</el-button>
|
||||||
|
<el-button @click="close">取消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "deviceEdit",
|
||||||
|
props: {},
|
||||||
|
computed: {},
|
||||||
|
created() {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
listChangeCallback: null,
|
||||||
|
showDialog: false,
|
||||||
|
isLoging: false,
|
||||||
|
form: {},
|
||||||
|
isEdit: false,
|
||||||
|
rules: {
|
||||||
|
deviceId: [{ required: true, message: "请输入设备编号", trigger: "blur" }]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openDialog: function (row, callback) {
|
||||||
|
console.log(row)
|
||||||
|
this.showDialog = true;
|
||||||
|
this.isEdit = false;
|
||||||
|
if (row) {
|
||||||
|
this.isEdit = true;
|
||||||
|
}
|
||||||
|
this.form = {};
|
||||||
|
this.listChangeCallback = callback;
|
||||||
|
if (row != null) {
|
||||||
|
this.form = row;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSubmit: function () {
|
||||||
|
console.log("onSubmit");
|
||||||
|
this.$axios({
|
||||||
|
method: 'post',
|
||||||
|
url:`/api/jt1078/terminal/${this.isEdit?'update':'add'}/`,
|
||||||
|
params: this.form
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res.data)
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
this.listChangeCallback()
|
||||||
|
}else {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: res.data.msg,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
this.showDialog = false;
|
||||||
|
this.$refs.form.resetFields();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
923
web/src/views/common/channelPlayer/jtDevicePlayer.vue
Executable file
923
web/src/views/common/channelPlayer/jtDevicePlayer.vue
Executable file
@ -0,0 +1,923 @@
|
|||||||
|
<template>
|
||||||
|
<div id="devicePlayer" v-loading="isLoging">
|
||||||
|
|
||||||
|
<el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" v-if="showVideoDialog">
|
||||||
|
<div style="width: 100%; height: 100%">
|
||||||
|
<el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer"
|
||||||
|
v-if="Object.keys(this.player).length > 1">
|
||||||
|
<el-tab-pane label="Jessibuca" name="jessibuca">
|
||||||
|
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog"
|
||||||
|
:error="videoError" :message="videoError"
|
||||||
|
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="WebRTC" name="webRTC">
|
||||||
|
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog"
|
||||||
|
:error="videoError" :message="videoError" height="100px"
|
||||||
|
:hasAudio="hasAudio" fluent autoplay live></rtc-player>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca"
|
||||||
|
:visible.sync="showVideoDialog" :error="videoError" :message="videoError"
|
||||||
|
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
|
||||||
|
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca"
|
||||||
|
:visible.sync="showVideoDialog" :error="videoError" :message="videoError"
|
||||||
|
height="100px" :hasAudio="hasAudio" fluent autoplay live></rtc-player>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="shared" style="text-align: right; margin-top: 1rem;">
|
||||||
|
<el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
|
||||||
|
<el-tab-pane label="实时视频" name="media">
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
|
||||||
|
<template slot="append">
|
||||||
|
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl"
|
||||||
|
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
|
||||||
|
<template slot="append">
|
||||||
|
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe"
|
||||||
|
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
|
||||||
|
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true">
|
||||||
|
<el-button slot="append" icon="el-icon-document-copy" title="点击拷贝"
|
||||||
|
v-clipboard="getPlayerShared.sharedRtmp"
|
||||||
|
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
|
||||||
|
<el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
|
||||||
|
<el-button>
|
||||||
|
更多地址<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item v-if="streamInfo.flv" :command="streamInfo.flv">
|
||||||
|
<el-tag>FLV:</el-tag>
|
||||||
|
<span>{{ streamInfo.flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_flv" :command="streamInfo.https_flv">
|
||||||
|
<el-tag>FLV(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_flv" :command="streamInfo.ws_flv">
|
||||||
|
<el-tag>FLV(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_flv" :command="streamInfo.wss_flv">
|
||||||
|
<el-tag>FLV(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_flv }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.fmp4" :command="streamInfo.fmp4">
|
||||||
|
<el-tag>FMP4:</el-tag>
|
||||||
|
<span>{{ streamInfo.fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_fmp4" :command="streamInfo.https_fmp4">
|
||||||
|
<el-tag>FMP4(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_fmp4" :command="streamInfo.ws_fmp4">
|
||||||
|
<el-tag>FMP4(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_fmp4" :command="streamInfo.wss_fmp4">
|
||||||
|
<el-tag>FMP4(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_fmp4 }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.hls" :command="streamInfo.hls">
|
||||||
|
<el-tag>HLS:</el-tag>
|
||||||
|
<span>{{ streamInfo.hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_hls" :command="streamInfo.https_hls">
|
||||||
|
<el-tag>HLS(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_hls" :command="streamInfo.ws_hls">
|
||||||
|
<el-tag>HLS(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_hls" :command="streamInfo.wss_hls">
|
||||||
|
<el-tag>HLS(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_hls }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ts" :command="streamInfo.ts">
|
||||||
|
<el-tag>TS:</el-tag>
|
||||||
|
<span>{{ streamInfo.ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.https_ts" :command="streamInfo.https_ts">
|
||||||
|
<el-tag>TS(https):</el-tag>
|
||||||
|
<span>{{ streamInfo.https_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.ws_ts" :command="streamInfo.ws_ts">
|
||||||
|
<el-tag>TS(ws):</el-tag>
|
||||||
|
<span>{{ streamInfo.ws_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.wss_ts" :command="streamInfo.wss_ts">
|
||||||
|
<el-tag>TS(wss):</el-tag>
|
||||||
|
<span>{{ streamInfo.wss_ts }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtc" :command="streamInfo.rtc">
|
||||||
|
<el-tag>RTC:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtc }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtcs" :command="streamInfo.rtcs">
|
||||||
|
<el-tag>RTCS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtcs }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtmp" :command="streamInfo.rtmp">
|
||||||
|
<el-tag>RTMP:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtmp }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtmps" :command="streamInfo.rtmps">
|
||||||
|
<el-tag>RTMPS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtmps }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtsp" :command="streamInfo.rtsp">
|
||||||
|
<el-tag>RTSP:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtsp }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="streamInfo.rtsps" :command="streamInfo.rtsps">
|
||||||
|
<el-tag>RTSPS:</el-tag>
|
||||||
|
<span>{{ streamInfo.rtsps }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
|
||||||
|
<!--遥控界面-->
|
||||||
|
<el-tab-pane label="云台控制" name="control" v-if="showPtz">
|
||||||
|
<div style="display: grid; grid-template-columns: 240px auto; height: 180px; overflow: auto">
|
||||||
|
<div style="display: grid; grid-template-columns: 6.25rem auto;">
|
||||||
|
|
||||||
|
<div class="control-wrapper">
|
||||||
|
<div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-top"></i>
|
||||||
|
<div class="control-inner-btn control-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-left" @mousedown="ptzCamera('left')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-left"></i>
|
||||||
|
<div class="control-inner-btn control-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-bottom" @mousedown="ptzCamera('down')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-bottom"></i>
|
||||||
|
<div class="control-inner-btn control-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="control-btn control-right" @mousedown="ptzCamera('right')" @mouseup="ptzCamera('stop')">
|
||||||
|
<i class="el-icon-caret-right"></i>
|
||||||
|
<div class="control-inner-btn control-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="control-round">
|
||||||
|
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 6.25rem;">
|
||||||
|
<el-slider v-model="controSpeed" :max="100"></el-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div style="" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')" title="变倍+">
|
||||||
|
<i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div style="" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')" title="变倍-">
|
||||||
|
<i class="el-icon-zoom-out control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div @mousedown="ptzCamera('focusnear')" @mouseup="ptzCamera('stop')" title="聚焦+">
|
||||||
|
<i class="iconfont icon-bianjiao-fangda control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div @mousedown="ptzCamera('focusfar')" @mouseup="ptzCamera('stop')" title="聚焦-">
|
||||||
|
<i class="iconfont icon-bianjiao-suoxiao control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ptz-btn-box">
|
||||||
|
<div @mousedown="ptzCamera('irisin')" @mouseup="ptzCamera('stop')" title="光圈+">
|
||||||
|
<i class="iconfont icon-guangquan control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div @mousedown="ptzCamera('irisout')" @mouseup="ptzCamera('stop')" title="光圈-">
|
||||||
|
<i class="iconfont icon-guangquan- control-zoom-btn" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: left" >
|
||||||
|
<div style="width: 100%; display: grid; grid-template-rows: 1fr 1fr; grid-row-gap: 10px">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button size="mini" @click="wiper('on')">开启雨刷
|
||||||
|
</el-button>
|
||||||
|
<el-button size="mini" @click="wiper('off')">关闭雨刷
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button size="mini" @click="fillLight('on')">开补光灯
|
||||||
|
</el-button>
|
||||||
|
<el-button size="mini" @click="fillLight('off')">关补光灯
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading">
|
||||||
|
<mediaInfo :app="app" :stream="streamId" :mediaServerId="mediaServerId"></mediaInfo>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="语音对讲" name="broadcast">
|
||||||
|
<div class="trank" style="text-align: center;">
|
||||||
|
<el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2"
|
||||||
|
circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import rtcPlayer from '../dialog/rtcPlayer.vue'
|
||||||
|
import LivePlayer from '@liveqing/liveplayer'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import jessibucaPlayer from '../common/jessibuca.vue'
|
||||||
|
import mediaInfo from '../common/mediaInfo.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'devicePlayer',
|
||||||
|
props: {},
|
||||||
|
components: {
|
||||||
|
mediaInfo,
|
||||||
|
LivePlayer, jessibucaPlayer, rtcPlayer,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getPlayerShared: function () {
|
||||||
|
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
|
||||||
|
const type = typeMap[this.activePlayer] || 0
|
||||||
|
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
|
||||||
|
return {
|
||||||
|
sharedUrl: baseUrl,
|
||||||
|
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
|
||||||
|
sharedRtmp: this.videoUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
console.log("created")
|
||||||
|
console.log(this.player)
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
if (Object.keys(this.player).length === 1) {
|
||||||
|
this.activePlayer = Object.keys(this.player)[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
|
||||||
|
videoUrl: '',
|
||||||
|
activePlayer: "jessibuca",
|
||||||
|
// 如何你只是用一种播放器,直接注释掉不用的部分即可
|
||||||
|
player: {
|
||||||
|
jessibuca: ["ws_flv", "wss_flv"],
|
||||||
|
webRTC: ["rtc", "rtcs"],
|
||||||
|
},
|
||||||
|
showVideoDialog: false,
|
||||||
|
streamId: '',
|
||||||
|
app: '',
|
||||||
|
mediaServerId: '',
|
||||||
|
deviceId: '',
|
||||||
|
channelId: '',
|
||||||
|
tabActiveName: 'media',
|
||||||
|
hasAudio: false,
|
||||||
|
loadingRecords: false,
|
||||||
|
recordsLoading: false,
|
||||||
|
isLoging: false,
|
||||||
|
controSpeed: 30,
|
||||||
|
timeVal: 0,
|
||||||
|
timeMin: 0,
|
||||||
|
timeMax: 1440,
|
||||||
|
presetPos: 1,
|
||||||
|
cruisingSpeed: 100,
|
||||||
|
cruisingTime: 5,
|
||||||
|
cruisingGroup: 0,
|
||||||
|
scanSpeed: 100,
|
||||||
|
scanGroup: 0,
|
||||||
|
tracks: [],
|
||||||
|
tracksLoading: false,
|
||||||
|
showPtz: true,
|
||||||
|
showRrecord: true,
|
||||||
|
tracksNotLoaded: false,
|
||||||
|
sliderTime: 0,
|
||||||
|
seekTime: 0,
|
||||||
|
recordStartTime: 0,
|
||||||
|
showTimeText: "00:00:00",
|
||||||
|
streamInfo: null,
|
||||||
|
broadcastMode: true,
|
||||||
|
broadcastRtc: null,
|
||||||
|
broadcastStatus: -1, // -2 正在释放资源 -1 默认状态 0 等待接通 1 接通成功
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tabHandleClick: function (tab, event) {
|
||||||
|
console.log(tab)
|
||||||
|
var that = this;
|
||||||
|
that.tracks = [];
|
||||||
|
that.tracksLoading = true;
|
||||||
|
that.tracksNotLoaded = false;
|
||||||
|
if (tab.name === "codec") {
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/zlm/' + this.mediaServerId + '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtsp&app=' + this.app + '&stream=' + this.streamId
|
||||||
|
}).then(function (res) {
|
||||||
|
that.tracksLoading = false;
|
||||||
|
if (res.data.code == 0 && res.data.tracks) {
|
||||||
|
that.tracks = res.data.tracks;
|
||||||
|
} else {
|
||||||
|
that.tracksNotLoaded = true;
|
||||||
|
that.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '获取编码信息失败,',
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function (e) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changePlayer: function (tab) {
|
||||||
|
console.log(this.player[tab.name][0])
|
||||||
|
this.activePlayer = tab.name;
|
||||||
|
this.videoUrl = this.getUrlByStreamInfo()
|
||||||
|
console.log(this.videoUrl)
|
||||||
|
if (this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].play(this.videoUrl)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs[this.activePlayer].play(this.videoUrl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openDialog: function (tab, deviceId, channelId, param) {
|
||||||
|
if (this.showVideoDialog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tabActiveName = tab;
|
||||||
|
this.channelId = channelId;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.streamId = "";
|
||||||
|
this.mediaServerId = "";
|
||||||
|
this.app = "";
|
||||||
|
this.videoUrl = ""
|
||||||
|
if (!!this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].pause();
|
||||||
|
}
|
||||||
|
switch (tab) {
|
||||||
|
case "media":
|
||||||
|
this.play(param.streamInfo, param.hasAudio)
|
||||||
|
break;
|
||||||
|
case "streamPlay":
|
||||||
|
this.tabActiveName = "media";
|
||||||
|
this.showRrecord = false;
|
||||||
|
this.showPtz = false;
|
||||||
|
this.play(param.streamInfo, param.hasAudio)
|
||||||
|
break;
|
||||||
|
case "control":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
play: function (streamInfo, hasAudio) {
|
||||||
|
this.streamInfo = streamInfo;
|
||||||
|
this.hasAudio = hasAudio;
|
||||||
|
this.isLoging = false;
|
||||||
|
// this.videoUrl = streamInfo.rtc;
|
||||||
|
this.videoUrl = this.getUrlByStreamInfo();
|
||||||
|
this.streamId = streamInfo.stream;
|
||||||
|
this.app = streamInfo.app;
|
||||||
|
this.mediaServerId = streamInfo.mediaServerId;
|
||||||
|
this.playFromStreamInfo(false, streamInfo)
|
||||||
|
},
|
||||||
|
getUrlByStreamInfo() {
|
||||||
|
console.log(this.streamInfo)
|
||||||
|
let streamInfo = this.streamInfo
|
||||||
|
if (this.streamInfo.transcodeStream) {
|
||||||
|
streamInfo = this.streamInfo.transcodeStream;
|
||||||
|
}
|
||||||
|
if (location.protocol === "https:") {
|
||||||
|
this.videoUrl = streamInfo[this.player[this.activePlayer][1]]
|
||||||
|
} else {
|
||||||
|
this.videoUrl = streamInfo[this.player[this.activePlayer][0]]
|
||||||
|
}
|
||||||
|
return this.videoUrl;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
playFromStreamInfo: function (realHasAudio, streamInfo) {
|
||||||
|
this.showVideoDialog = true;
|
||||||
|
this.hasaudio = realHasAudio && this.hasaudio;
|
||||||
|
if (this.$refs[this.activePlayer]) {
|
||||||
|
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||||
|
}else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
console.log('关闭视频');
|
||||||
|
if (!!this.$refs[this.activePlayer]){
|
||||||
|
this.$refs[this.activePlayer].pause();
|
||||||
|
}
|
||||||
|
this.videoUrl = '';
|
||||||
|
this.coverPlaying = false;
|
||||||
|
this.showVideoDialog = false;
|
||||||
|
this.stopBroadcast()
|
||||||
|
},
|
||||||
|
|
||||||
|
copySharedInfo: function (data) {
|
||||||
|
console.log('复制内容:' + data);
|
||||||
|
this.coverPlaying = false;
|
||||||
|
this.tracks = []
|
||||||
|
let _this = this;
|
||||||
|
this.$copyText(data).then(
|
||||||
|
function (e) {
|
||||||
|
_this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '复制成功',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function (e) {
|
||||||
|
_this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '复制失败,请手动复制',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ptzCamera: function (command) {
|
||||||
|
console.log('云台控制:' + command);
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/jt1078/ptz',
|
||||||
|
params: {
|
||||||
|
phoneNumber: this.deviceId,
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
speed: this.controSpeed,
|
||||||
|
|
||||||
|
}
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
wiper: function (command) {
|
||||||
|
console.log('雨刷控制:' + command);
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/jt1078/wiper',
|
||||||
|
params: {
|
||||||
|
phoneNumber: this.deviceId,
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
}
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fillLight: function (command) {
|
||||||
|
console.log('补光灯开关控制:' + command);
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/jt1078/fill-light',
|
||||||
|
params: {
|
||||||
|
phoneNumber: this.deviceId,
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
}
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//////////////////////播放器事件处理//////////////////////////
|
||||||
|
videoError: function (e) {
|
||||||
|
console.log("播放器错误:" + JSON.stringify(e));
|
||||||
|
},
|
||||||
|
presetPosition: function (cmdCode, presetPos) {
|
||||||
|
console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16));
|
||||||
|
let that = this;
|
||||||
|
this.$axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=0¶meter2=' + presetPos + '&combindCode2=0'
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setSpeedOrTime: function (cmdCode, groupNum, parameter) {
|
||||||
|
let that = this;
|
||||||
|
let parameter2 = parameter % 256;
|
||||||
|
let combindCode2 = Math.floor(parameter / 256) * 16;
|
||||||
|
console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16));
|
||||||
|
this.$axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter2 + '&combindCode2=' + combindCode2
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setCommand: function (cmdCode, groupNum, parameter) {
|
||||||
|
let that = this;
|
||||||
|
console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0');
|
||||||
|
this.$axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter + '&combindCode2=0'
|
||||||
|
}).then(function (res) {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
copyUrl: function (dropdownItem) {
|
||||||
|
console.log(dropdownItem)
|
||||||
|
this.$copyText(dropdownItem).then((e) => {
|
||||||
|
this.$message.success({
|
||||||
|
showClose: true,
|
||||||
|
message: "成功拷贝到粘贴板"
|
||||||
|
})
|
||||||
|
}, (e) => {
|
||||||
|
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getBroadcastStatus() {
|
||||||
|
if (this.broadcastStatus == -2) {
|
||||||
|
return "primary"
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus == -1) {
|
||||||
|
return "primary"
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus == 0) {
|
||||||
|
return "warning"
|
||||||
|
}
|
||||||
|
if (this.broadcastStatus == 1) {
|
||||||
|
return "danger"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
broadcastStatusClick() {
|
||||||
|
if (this.broadcastStatus == -1) {
|
||||||
|
// 默认状态, 开始
|
||||||
|
this.broadcastStatus = 0
|
||||||
|
// 发起语音对讲
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30&broadcastMode=" + this.broadcastMode
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
let streamInfo = res.data.data.streamInfo;
|
||||||
|
if (document.location.protocol.includes("https")) {
|
||||||
|
this.startBroadcast(streamInfo.rtcs)
|
||||||
|
} else {
|
||||||
|
this.startBroadcast(streamInfo.rtc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: res.data.msg,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.broadcastStatus === 1) {
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
this.broadcastRtc.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startBroadcast(url) {
|
||||||
|
// 获取推流鉴权Key
|
||||||
|
this.$axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/user/userInfo',
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data.code !== 0) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: "获取推流鉴权Key失败",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
} else {
|
||||||
|
let pushKey = res.data.data.pushKey;
|
||||||
|
// 获取推流鉴权KEY
|
||||||
|
url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
|
||||||
|
console.log("开始语音喊话: " + url)
|
||||||
|
this.broadcastRtc = new ZLMRTCClient.Endpoint({
|
||||||
|
debug: true, // 是否打印日志
|
||||||
|
zlmsdpUrl: url, //流地址
|
||||||
|
simulecast: false,
|
||||||
|
useCamera: false,
|
||||||
|
audioEnable: true,
|
||||||
|
videoEnable: false,
|
||||||
|
recvOnly: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放
|
||||||
|
// console.error('播放成功',e.streams)
|
||||||
|
// this.broadcastStatus = 1;
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{// 获取到了本地流
|
||||||
|
// this.broadcastStatus = 1;
|
||||||
|
// // document.getElementById('selfVideo').srcObject=s;
|
||||||
|
// // this.eventcallbacK("LOCAL STREAM", "获取到了本地流")
|
||||||
|
// });
|
||||||
|
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {// 获取到了本地流
|
||||||
|
console.error('不支持webrtc', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '不支持webrtc, 无法进行语音喊话',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错
|
||||||
|
console.error('ICE 协商出错')
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'ICE 协商出错',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {// offer anwser 交换失败
|
||||||
|
console.error('offer anwser 交换失败', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'offer anwser 交换失败' + e,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
});
|
||||||
|
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => {// offer anwser 交换失败
|
||||||
|
console.log('状态改变', 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, (e) => {// offer anwser 交换失败
|
||||||
|
console.log('捕获流失败', e)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '捕获流失败' + e,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: e,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
stopBroadcast() {
|
||||||
|
this.broadcastRtc.close();
|
||||||
|
this.broadcastStatus = -1;
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/play/broadcast/stop/' + this.deviceId + '/' + this.channelId
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data.code == 0) {
|
||||||
|
// this.broadcastStatus = -1;
|
||||||
|
// this.broadcastRtc.close()
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: res.data.msg,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.control-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 6.25rem;
|
||||||
|
height: 6.25rem;
|
||||||
|
max-width: 6.25rem;
|
||||||
|
max-height: 6.25rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 5rem;
|
||||||
|
height: 11rem;
|
||||||
|
max-height: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
width: 44%;
|
||||||
|
height: 44%;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #78aee4;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #78aee4;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn i:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-zoom-btn:hover {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-round {
|
||||||
|
position: absolute;
|
||||||
|
top: 21%;
|
||||||
|
left: 21%;
|
||||||
|
width: 58%;
|
||||||
|
height: 58%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-round-inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 13%;
|
||||||
|
top: 13%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
font-size: 40px;
|
||||||
|
color: #78aee4;
|
||||||
|
border: 1px solid #78aee4;
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-inner-btn {
|
||||||
|
position: absolute;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top {
|
||||||
|
top: -8%;
|
||||||
|
left: 27%;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top i {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top .control-inner {
|
||||||
|
left: -1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 100% 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-top .fa {
|
||||||
|
transform: rotate(45deg) translateY(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left {
|
||||||
|
top: 27%;
|
||||||
|
left: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 0 5px 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left .control-inner {
|
||||||
|
right: -1px;
|
||||||
|
top: -1px;
|
||||||
|
border-bottom: 1px solid #78aee4;
|
||||||
|
border-left: 1px solid #78aee4;
|
||||||
|
border-radius: 0 0 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-left .fa {
|
||||||
|
transform: rotate(-45deg) translateX(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right {
|
||||||
|
top: 27%;
|
||||||
|
right: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 5px 100% 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right .control-inner {
|
||||||
|
left: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
border-top: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 100% 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-right .fa {
|
||||||
|
transform: rotate(-45deg) translateX(7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom {
|
||||||
|
left: 27%;
|
||||||
|
bottom: -8%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 0 5px 100% 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom i {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom .control-inner {
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
border-bottom: 1px solid #78aee4;
|
||||||
|
border-right: 1px solid #78aee4;
|
||||||
|
border-radius: 0 0 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bottom .fa {
|
||||||
|
transform: rotate(-45deg) translateY(7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trank {
|
||||||
|
width: 80%;
|
||||||
|
height: 180px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 10%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trankInfo {
|
||||||
|
width: 80%;
|
||||||
|
padding: 0 10%;
|
||||||
|
}
|
||||||
|
.el-dialog__body{
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.ptz-btn-box {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
363
web/src/views/common/channelPlayer/ptzCruising.vue
Normal file
363
web/src/views/common/channelPlayer/ptzCruising.vue
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzCruising">
|
||||||
|
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||||
|
<span>巡航组号: </span>
|
||||||
|
<el-input
|
||||||
|
v-model="tourId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="巡航组号"
|
||||||
|
addon-before="巡航组号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<el-tag
|
||||||
|
v-for="(item, index) in presetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
closable
|
||||||
|
style="margin-right: 1rem; cursor: pointer"
|
||||||
|
@close="delPreset(item, index)"
|
||||||
|
>
|
||||||
|
{{ item.presetName ? item.presetName : item.presetId }}
|
||||||
|
</el-tag>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<el-form v-if="selectPresetVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="selectPreset" value-key="presetId" placeholder="请选择预置点">
|
||||||
|
<el-option
|
||||||
|
v-for="item in allPresetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
:label="item.presetName ? item.presetName : item.presetId"
|
||||||
|
:value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="addCruisePoint">保存</el-button>
|
||||||
|
<el-button type="primary" @click="cancelAddCruisePoint">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="selectPresetVisible=true">添加巡航点</el-button>
|
||||||
|
|
||||||
|
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-if="setSpeedVisible"
|
||||||
|
v-model="cruiseSpeed"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航速度"
|
||||||
|
addon-before="巡航速度"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setCruiseSpeed">保存</el-button>
|
||||||
|
<el-button @click="cancelSetCruiseSpeed">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setSpeedVisible = true">设置巡航速度</el-button>
|
||||||
|
<el-form v-if="setTimeVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="cruiseTime"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航停留时间(秒)"
|
||||||
|
addon-before="巡航停留时间(秒)"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setCruiseTime">保存</el-button>
|
||||||
|
<el-button @click="cancelSetCruiseTime">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setTimeVisible = true">设置巡航时间</el-button>
|
||||||
|
<el-button size="mini" @click="startCruise">开始巡航</el-button>
|
||||||
|
<el-button size="mini" @click="stopCruise">停止巡航</el-button>
|
||||||
|
<el-button size="mini" type="danger" @click="deleteCruise">删除巡航</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzCruising',
|
||||||
|
components: {},
|
||||||
|
props: ['channelId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tourId: 1,
|
||||||
|
presetList: [],
|
||||||
|
allPresetList: [],
|
||||||
|
selectPreset: '',
|
||||||
|
inputVisible: false,
|
||||||
|
selectPresetVisible: false,
|
||||||
|
setSpeedVisible: false,
|
||||||
|
setTimeVisible: false,
|
||||||
|
cruiseSpeed: '',
|
||||||
|
cruiseTime: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getPresetList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPresetList: function() {
|
||||||
|
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
|
||||||
|
.then((data) => {
|
||||||
|
this.allPresetList = data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addCruisePoint: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/addPointForCruise',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId,
|
||||||
|
presetId: this.selectPreset.presetId
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList.push(this.selectPreset)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.selectPreset = ''
|
||||||
|
this.selectPresetVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelAddCruisePoint: function() {
|
||||||
|
this.selectPreset = ''
|
||||||
|
this.selectPresetVisible = false
|
||||||
|
},
|
||||||
|
delPreset: function(preset, index) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/deletePointForCruise',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId,
|
||||||
|
presetId: preset.presetId
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList.splice(index, 1)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteCruise: function(preset, index) {
|
||||||
|
this.$confirm('确定删除此巡航组', '提示', {
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/deletePointForCruise',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId,
|
||||||
|
presetId: 0
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList = []
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setCruiseSpeed: function() {
|
||||||
|
if (this.presetList.length === 0) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '请添加巡航点',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/setCruiseSpeed',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId,
|
||||||
|
presetId: this.presetList.at(-1).presetId,
|
||||||
|
speed: this.cruiseSpeed
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.cruiseSpeed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetCruiseSpeed: function() {
|
||||||
|
this.cruiseSpeed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
},
|
||||||
|
setCruiseTime: function() {
|
||||||
|
if (this.presetList.length === 0) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '请添加巡航点',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/setCruiseTime',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId,
|
||||||
|
time: this.cruiseTime,
|
||||||
|
presetId: this.presetList.at(-1).presetId
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetCruiseTime: function() {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
},
|
||||||
|
startCruise: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/startCruise',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stopCruise: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/stopCruise',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
tourId: this.tourId
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
162
web/src/views/common/channelPlayer/ptzPreset.vue
Normal file
162
web/src/views/common/channelPlayer/ptzPreset.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzPreset" style="width: 100%">
|
||||||
|
<el-tag
|
||||||
|
v-for="item in presetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
closable
|
||||||
|
size="mini"
|
||||||
|
style="margin-right: 1rem; cursor: pointer; margin-bottom: 0.6rem"
|
||||||
|
@close="delPreset(item)"
|
||||||
|
@click="gotoPreset(item)"
|
||||||
|
>
|
||||||
|
{{ item.presetName?item.presetName:item.presetId }}
|
||||||
|
</el-tag>
|
||||||
|
<el-input
|
||||||
|
v-if="inputVisible"
|
||||||
|
ref="saveTagInput"
|
||||||
|
v-model="ptzPresetId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="预置位编号"
|
||||||
|
addon-before="预置位编号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
style="width: 300px; vertical-align: bottom;"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<el-button @click="addPreset()">保存</el-button>
|
||||||
|
<el-button @click="cancel()">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button v-else size="small" @click="showInput">+ 添加</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzPreset',
|
||||||
|
components: {},
|
||||||
|
props: ['channelId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
presetList: [],
|
||||||
|
inputVisible: false,
|
||||||
|
ptzPresetId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getPresetList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPresetList: function() {
|
||||||
|
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
|
||||||
|
.then(data => {
|
||||||
|
this.presetList = data
|
||||||
|
// 防止出现表格错位
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.channelListTable.doLayout()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showInput() {
|
||||||
|
this.inputVisible = true
|
||||||
|
this.$nextTick(_ => {
|
||||||
|
this.$refs.saveTagInput.$refs.input.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addPreset: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/addPreset', {
|
||||||
|
channelId: this.channelId,
|
||||||
|
presetId: this.ptzPresetId,
|
||||||
|
presetName: this.ptzPresetId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
this.getPresetList()
|
||||||
|
}, 1000)
|
||||||
|
}).catch((error) => {
|
||||||
|
loading.close()
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel: function() {
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
},
|
||||||
|
gotoPreset: function(preset) {
|
||||||
|
console.log(preset)
|
||||||
|
this.$store.dispatch('commonChanel/callPreset', {
|
||||||
|
channelId: this.channelId,
|
||||||
|
presetId: preset.presetId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '调用成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delPreset: function(preset) {
|
||||||
|
this.$confirm('确定删除此预置位', '提示', {
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/deletePreset', {
|
||||||
|
channelId: this.channelId,
|
||||||
|
presetId: preset.presetId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getPresetList()
|
||||||
|
}, 1000)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
233
web/src/views/common/channelPlayer/ptzScan.vue
Normal file
233
web/src/views/common/channelPlayer/ptzScan.vue
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzScan">
|
||||||
|
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||||
|
<span>扫描组号: </span>
|
||||||
|
<el-input
|
||||||
|
v-model="scanId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="扫描组号"
|
||||||
|
addon-before="扫描组号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button size="mini" @click="setScanLeft">设置左边界</el-button>
|
||||||
|
<el-button size="mini" @click="setScanRight">设置右边界</el-button>
|
||||||
|
|
||||||
|
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-if="setSpeedVisible"
|
||||||
|
v-model="speed"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航速度"
|
||||||
|
addon-before="巡航速度"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setSpeed">保存</el-button>
|
||||||
|
<el-button @click="cancelSetSpeed">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setSpeedVisible = true">设置扫描速度</el-button>
|
||||||
|
|
||||||
|
<el-button size="mini" @click="startScan">开始自动扫描</el-button>
|
||||||
|
<el-button size="mini" @click="stopScan">停止自动扫描</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzScan',
|
||||||
|
components: {},
|
||||||
|
props: ['channelId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
scanId: 1,
|
||||||
|
setSpeedVisible: false,
|
||||||
|
speed: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setSpeed: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/setSpeedForScan',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
scanId: this.scanId,
|
||||||
|
speed: this.speed
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetSpeed: function() {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
},
|
||||||
|
setScanLeft: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/setLeftForScan',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
scanId: this.scanId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setScanRight: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/setRightForScan',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
scanId: this.scanId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
startScan: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/startScan',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
scanId: this.scanId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stopScan: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/stopScan',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
scanId: this.scanId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
72
web/src/views/common/channelPlayer/ptzSwitch.vue
Normal file
72
web/src/views/common/channelPlayer/ptzSwitch.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzScan">
|
||||||
|
<el-form size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="auxiliaryId"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="开关编号"
|
||||||
|
addon-before="开关编号"
|
||||||
|
addon-after="(2-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||||
|
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzScan',
|
||||||
|
components: {},
|
||||||
|
props: ['channelId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
auxiliaryId: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open: function(command) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/auxiliary',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command,
|
||||||
|
auxiliaryId: this.auxiliaryId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
62
web/src/views/common/channelPlayer/ptzWiper.vue
Normal file
62
web/src/views/common/channelPlayer/ptzWiper.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzWiper">
|
||||||
|
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||||
|
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzWiper',
|
||||||
|
components: {},
|
||||||
|
props: ['channelId'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open: function(command) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('commonChanel/wiper',
|
||||||
|
{
|
||||||
|
channelId: this.channelId,
|
||||||
|
command: command
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :id="'h265Player-' + _uid" ref="container" style="background-color: #000000; position: relative; display: flex; align-items: center; justify-content: center;" @dblclick="fullscreenSwich" @mouseenter="showBar = true" @mouseleave="showBar = false">
|
<div id="h265Player" ref="container" style="background-color: #000000; " @dblclick="fullscreenSwich">
|
||||||
<div :id="'glplayer-' + _uid" ref="playerBox" style="width: 100%; height: 100%; margin: 0 auto;">
|
<div id="glplayer" ref="playerBox" style="width: 100%; height: 100%; margin: 0 auto;" >
|
||||||
<div v-if="playerLoading" class="play-loading">
|
<div v-if="playerLoading" class="play-loading">
|
||||||
<i class="el-icon-loading" />
|
<i class="el-icon-loading" />
|
||||||
<span style="margin-left: 5px">视频加载中</span>
|
视频加载中
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showButton" id="buttonsBox" class="buttons-box" :style="{ opacity: showBar ? 1 : 0, pointerEvents: showBar ? 'auto' : 'none' }">
|
<div v-if="showButton" id="buttonsBox" class="buttons-box">
|
||||||
<div class="buttons-box-left">
|
<div class="buttons-box-left">
|
||||||
<i v-if="!playing" class="iconfont icon-play h265web-btn" @click="unPause" />
|
<i v-if="!playing" class="iconfont icon-play h265web-btn" @click="unPause" />
|
||||||
<i v-if="playing" class="iconfont icon-pause h265web-btn" @click="pause" />
|
<i v-if="playing" class="iconfont icon-pause h265web-btn" @click="pause" />
|
||||||
@ -38,10 +38,8 @@ 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 {
|
||||||
@ -62,8 +60,7 @@ export default {
|
|||||||
playerHeight: 0,
|
playerHeight: 0,
|
||||||
inited: false,
|
inited: false,
|
||||||
playerLoading: false,
|
playerLoading: false,
|
||||||
mediaInfo: null,
|
mediaInfo: null
|
||||||
showBar: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -122,7 +119,7 @@ export default {
|
|||||||
const options = {}
|
const options = {}
|
||||||
h265webPlayer[this._uid] = new window.new265webjs(url, Object.assign(
|
h265webPlayer[this._uid] = new window.new265webjs(url, Object.assign(
|
||||||
{
|
{
|
||||||
player: 'glplayer-' + this._uid,
|
player: 'glplayer', // 播放器容器id
|
||||||
width: this.playerWidth,
|
width: this.playerWidth,
|
||||||
height: this.playerHeight,
|
height: this.playerHeight,
|
||||||
token: token,
|
token: token,
|
||||||
@ -249,12 +246,6 @@ 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,15 +264,12 @@ export default {
|
|||||||
}
|
}
|
||||||
.buttons-box {
|
.buttons-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 56px;
|
height: 28px;
|
||||||
background: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
|
background-color: rgba(43, 51, 63, 0.7);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -291,6 +279,7 @@ export default {
|
|||||||
.h265web-btn {
|
.h265web-btn {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
color: rgb(255, 255, 255);
|
color: rgb(255, 255, 255);
|
||||||
|
line-height: 27px;
|
||||||
margin: 0px 10px;
|
margin: 0px 10px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -301,7 +290,6 @@ export default {
|
|||||||
.buttons-box-right {
|
.buttons-box-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 10px;
|
|
||||||
}
|
}
|
||||||
.player-loading {
|
.player-loading {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
|||||||
@ -1,41 +1,38 @@
|
|||||||
<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"
|
>
|
||||||
>
|
<div id="buttonsBox" class="buttons-box" v-if="showButton === undefined || showButton">
|
||||||
<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 {
|
||||||
@ -54,8 +51,7 @@ export default {
|
|||||||
rotate: 0,
|
rotate: 0,
|
||||||
vod: true, // 点播
|
vod: true, // 点播
|
||||||
forceNoOffscreen: false,
|
forceNoOffscreen: false,
|
||||||
localVideoUrl: this.videoUrl,
|
localVideoUrl: this.videoUrl
|
||||||
showBar: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -282,14 +278,6 @@ 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +289,8 @@ export default {
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
background-color: rgba(43, 51, 63, 0.7);
|
background-color: rgba(43, 51, 63, 0.7);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transition: opacity 0.3s ease;
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -313,7 +302,7 @@ export default {
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
color: rgb(255, 255, 255);
|
color: rgb(255, 255, 255);
|
||||||
line-height: 27px;
|
line-height: 27px;
|
||||||
margin: 0px 20px;
|
margin: 0px 10px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -91,6 +91,11 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
#mediaInfo { position: relative; }
|
.channel-form {
|
||||||
#mediaInfo >>> .el-descriptions__title { font-size: 14px; color: #606266; font-weight: 600; }
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,185 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-tabs-wrapper" ref="playerWrapper">
|
|
||||||
<el-tabs v-if="showTab && playerList.length > 1" v-model="activePlayer" type="card" :stretch="true" @tab-click="changePlayer">
|
|
||||||
<el-tab-pane v-for="p in playerList" :key="p.key" :label="p.label" :name="p.key"></el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
<div class="player-video-area" :style="{ height: showTab ? 'calc(100% - 36px)' : '100%' }">
|
|
||||||
<jessibucaPlayer
|
|
||||||
v-if="activePlayer === 'jessibuca'"
|
|
||||||
ref="jessibuca"
|
|
||||||
style="width: 100%; height: 100%;"
|
|
||||||
:has-audio="hasAudio"
|
|
||||||
:show-button="showButton"
|
|
||||||
fluent autoplay live
|
|
||||||
@playTimeChange="$emit('playTimeChange', $event)"
|
|
||||||
@playStatusChange="$emit('playStatusChange', $event)"
|
|
||||||
/>
|
|
||||||
<rtc-player
|
|
||||||
v-if="activePlayer === 'webRTC'"
|
|
||||||
ref="webRTC"
|
|
||||||
style="width: 100%; height: 100%;"
|
|
||||||
:has-audio="hasAudio"
|
|
||||||
:show-button="showButton"
|
|
||||||
fluent autoplay live
|
|
||||||
@playTimeChange="$emit('playTimeChange', $event)"
|
|
||||||
@playStatusChange="$emit('playStatusChange', $event)"
|
|
||||||
/>
|
|
||||||
<h265web
|
|
||||||
v-if="activePlayer === 'h265web'"
|
|
||||||
ref="h265web"
|
|
||||||
style="width: 100%; height: 100%;"
|
|
||||||
:has-audio="hasAudio"
|
|
||||||
:show-button="showButton"
|
|
||||||
fluent autoplay live
|
|
||||||
@playTimeChange="$emit('playTimeChange', $event)"
|
|
||||||
@playStatusChange="$emit('playStatusChange', $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import jessibucaPlayer from './jessibuca.vue'
|
|
||||||
import rtcPlayer from './rtcPlayer.vue'
|
|
||||||
import h265web from './h265web.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PlayerTabs',
|
|
||||||
components: { jessibucaPlayer, rtcPlayer, h265web },
|
|
||||||
props: {
|
|
||||||
hasAudio: { type: Boolean, default: false },
|
|
||||||
showButton: { type: Boolean, default: true },
|
|
||||||
showTab: { type: Boolean, default: true }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
streamInfo: null,
|
|
||||||
activePlayer: 'jessibuca',
|
|
||||||
player: { jessibuca: ['ws_flv', 'wss_flv'], webRTC: ['rtc', 'rtcs'], h265web: ['ws_flv', 'wss_flv'] },
|
|
||||||
allPlayerList: [
|
|
||||||
{ key: 'jessibuca', label: 'Jessibuca' },
|
|
||||||
{ key: 'webRTC', label: 'WebRTC' },
|
|
||||||
{ key: 'h265web', label: 'H265web' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
playerList() {
|
|
||||||
return this.allPlayerList
|
|
||||||
},
|
|
||||||
playerCount() {
|
|
||||||
return this.playerList.length
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.playerCount === 1) {
|
|
||||||
this.activePlayer = this.playerList[0].key
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPlayerList() {
|
|
||||||
return this.playerList
|
|
||||||
},
|
|
||||||
getActivePlayer() {
|
|
||||||
return this.activePlayer
|
|
||||||
},
|
|
||||||
switchPlayer(key) {
|
|
||||||
if (this.activePlayer === key) return
|
|
||||||
this.activePlayer = key
|
|
||||||
if (this.streamInfo) {
|
|
||||||
this.play()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getUrlByStreamInfo() {
|
|
||||||
if (!this.streamInfo) return ''
|
|
||||||
if (location.protocol === 'https:') {
|
|
||||||
return this.streamInfo[this.player[this.activePlayer][1]]
|
|
||||||
}
|
|
||||||
return this.streamInfo[this.player[this.activePlayer][0]]
|
|
||||||
},
|
|
||||||
changePlayer(tab) {
|
|
||||||
this.activePlayer = tab.name
|
|
||||||
this.play()
|
|
||||||
this.$emit('player-changed', this.activePlayer)
|
|
||||||
},
|
|
||||||
setStreamInfo(streamInfo) {
|
|
||||||
this.streamInfo = streamInfo
|
|
||||||
this.play()
|
|
||||||
},
|
|
||||||
play() {
|
|
||||||
let playUrl = this.getUrlByStreamInfo()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs[this.activePlayer]) {
|
|
||||||
this.$refs[this.activePlayer].play(playUrl)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
|
|
||||||
const type = typeMap[this.activePlayer] || 0
|
|
||||||
const playerUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(playUrl)
|
|
||||||
this.$emit('playerChanged', { playUrl, playerUrl })
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
if (this.$refs[this.activePlayer]) {
|
|
||||||
this.$refs[this.activePlayer].pause()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pause() {
|
|
||||||
if (this.$refs[this.activePlayer]) {
|
|
||||||
this.$refs[this.activePlayer].pause()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
const player = this.$refs[this.activePlayer]
|
|
||||||
if (player && player.destroy) {
|
|
||||||
player.destroy()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setPlaybackRate(rate) {
|
|
||||||
const player = this.$refs[this.activePlayer]
|
|
||||||
if (player && player.setPlaybackRate) {
|
|
||||||
player.setPlaybackRate(rate)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resize(width, height) {
|
|
||||||
const player = this.$refs[this.activePlayer]
|
|
||||||
if (player && player.resize) {
|
|
||||||
player.resize(width, height)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
screenshot() {
|
|
||||||
const player = this.$refs[this.activePlayer]
|
|
||||||
if (player && player.screenshot) {
|
|
||||||
return player.screenshot()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.player-tabs-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.player-tabs-wrapper .el-tabs {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.player-tabs-wrapper .el-tabs >>> .el-tabs__header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.player-video-area {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,305 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="ptz-section-inner">
|
|
||||||
<div class="ptz-top">
|
|
||||||
<div v-if="hasPtzDirection" class="ptz-dpad">
|
|
||||||
<div class="dpad-ring"></div>
|
|
||||||
<button class="dpad-btn card card-up" @mousedown.prevent="handlePtzMove('up')" @mouseup.prevent="handlePtzStop()">▲</button>
|
|
||||||
<button class="dpad-btn card card-right" @mousedown.prevent="handlePtzMove('right')" @mouseup.prevent="handlePtzStop()">▶</button>
|
|
||||||
<button class="dpad-btn card card-down" @mousedown.prevent="handlePtzMove('down')" @mouseup.prevent="handlePtzStop()">▼</button>
|
|
||||||
<button class="dpad-btn card card-left" @mousedown.prevent="handlePtzMove('left')" @mouseup.prevent="handlePtzStop()">◀</button>
|
|
||||||
<button v-if="showDiagonals" class="dpad-btn diag diag-upright" @mousedown.prevent="handlePtzMove('upright')" @mouseup.prevent="handlePtzStop()"><span style="display:inline-block;transform:rotate(45deg)">▲</span></button>
|
|
||||||
<button v-if="showDiagonals" class="dpad-btn diag diag-downright" @mousedown.prevent="handlePtzMove('downright')" @mouseup.prevent="handlePtzStop()"><span style="display:inline-block;transform:rotate(135deg)">▲</span></button>
|
|
||||||
<button v-if="showDiagonals" class="dpad-btn diag diag-downleft" @mousedown.prevent="handlePtzMove('downleft')" @mouseup.prevent="handlePtzStop()"><span style="display:inline-block;transform:rotate(225deg)">▲</span></button>
|
|
||||||
<button v-if="showDiagonals" class="dpad-btn diag diag-upleft" @mousedown.prevent="handlePtzMove('upleft')" @mouseup.prevent="handlePtzStop()"><span style="display:inline-block;transform:rotate(-45deg)">▲</span></button>
|
|
||||||
<button class="dpad-btn dpad-center" title="停止" @click="$emit('ptz-stop')">⏹</button>
|
|
||||||
</div>
|
|
||||||
<div class="ptz-func-col">
|
|
||||||
<div class="ptz-func-group" :class="{ row: btnLayout === 'row' }">
|
|
||||||
<div class="ptz-func-row" v-if="homePosition && hasGuard">
|
|
||||||
<div class="ptz-func-row">
|
|
||||||
<div class="ptz-func-btn" title="看守位" @click.prevent="$emit('ptz-guard')">
|
|
||||||
<i class="el-icon-s-home" /><span>看守位</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="hasPtzDirection" class="ptz-func-row">
|
|
||||||
<div class="ptz-func-btn" title="变倍+" @mousedown.prevent="handlePtzMove('zoomin')" @mouseup.prevent="handlePtzStop()">
|
|
||||||
<i class="el-icon-zoom-in" /><span>变倍+</span>
|
|
||||||
</div>
|
|
||||||
<div class="ptz-func-btn" title="变倍-" @mousedown.prevent="handlePtzMove('zoomout')" @mouseup.prevent="handlePtzStop()">
|
|
||||||
<i class="el-icon-zoom-out" /><span>变倍-</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="hasFocus" class="ptz-func-row">
|
|
||||||
<div class="ptz-func-btn" title="聚焦+" @mousedown.prevent="$emit('focus-move', { command: 'near', speed: controSpeed })" @mouseup.prevent="$emit('focus-stop')">
|
|
||||||
<i class="iconfont icon-bianjiao-fangda" /><span>聚焦+</span>
|
|
||||||
</div>
|
|
||||||
<div class="ptz-func-btn" title="聚焦-" @mousedown.prevent="$emit('focus-move', { command: 'far', speed: controSpeed })" @mouseup.prevent="$emit('focus-stop')">
|
|
||||||
<i class="iconfont icon-bianjiao-suoxiao" /><span>聚焦-</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="hasIris" class="ptz-func-row">
|
|
||||||
<div class="ptz-func-btn" title="光圈+" @mousedown.prevent="$emit('iris-move', { command: 'in', speed: controSpeed })" @mouseup.prevent="$emit('iris-stop')">
|
|
||||||
<i class="iconfont icon-guangquan" /><span>光圈+</span>
|
|
||||||
</div>
|
|
||||||
<div class="ptz-func-btn" title="光圈-" @mousedown.prevent="$emit('iris-move', { command: 'out', speed: controSpeed })" @mouseup.prevent="$emit('iris-stop')">
|
|
||||||
<i class="iconfont icon-guangquan-" /><span>光圈-</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="hasDragZoom" class="ptz-func-row">
|
|
||||||
<div class="ptz-func-btn" title="拉框放大" @click="$emit('toggle-drag-zoom')">
|
|
||||||
<i class="iconfont icon-guangquan" /><span>拉框放大</span>
|
|
||||||
</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 v-if="hasAnyPtz" class="ptz-bottom">
|
|
||||||
<div class="slider-with-controls">
|
|
||||||
<span class="slider-label">速度</span>
|
|
||||||
<el-button type="text" icon="el-icon-minus" class="slider-btn" @click="adjustSpeed(-1)" />
|
|
||||||
<el-slider v-model="controSpeed" :max="100" :min="1" />
|
|
||||||
<el-button type="text" icon="el-icon-plus" class="slider-btn" @click="adjustSpeed(1)" />
|
|
||||||
<span class="slider-value">{{ controSpeed }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzControls',
|
|
||||||
props: {
|
|
||||||
btnLayout: { type: String, default: 'column' },
|
|
||||||
homePosition: { type: Boolean, default: false },
|
|
||||||
showDiagonals: { type: Boolean, default: true }
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasPtzDirection() { return 'ptz-move' in this.$listeners },
|
|
||||||
hasFocus() { return 'focus-move' in this.$listeners },
|
|
||||||
hasIris() { return 'iris-move' in this.$listeners },
|
|
||||||
hasDragZoom() { return 'toggle-drag-zoom' in this.$listeners || 'toggle-drag-zoom-out' in this.$listeners },
|
|
||||||
hasGuard() { return 'ptz-guard' in this.$listeners },
|
|
||||||
hasAnyPtz() { return this.hasPtzDirection || this.hasFocus || this.hasIris || this.hasDragZoom || this.hasGuard }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
controSpeed: 50,
|
|
||||||
currentCommand: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
window.addEventListener('mouseup', this.onWindowMouseUp)
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
window.removeEventListener('mouseup', this.onWindowMouseUp)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
adjustSpeed(delta) {
|
|
||||||
const newVal = this.controSpeed + delta
|
|
||||||
if (newVal >= 1 && newVal <= 100) {
|
|
||||||
this.controSpeed = newVal
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handlePtzMove(direction) {
|
|
||||||
this.currentCommand = direction
|
|
||||||
this.$emit('ptz-move', { direction, speed: this.controSpeed })
|
|
||||||
},
|
|
||||||
handlePtzStop() {
|
|
||||||
this.$emit('ptz-stop', { direction: this.currentCommand })
|
|
||||||
this.currentCommand = null
|
|
||||||
},
|
|
||||||
onWindowMouseUp() {
|
|
||||||
if (this.currentCommand) {
|
|
||||||
this.handlePtzStop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.ptz-section-inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 8px 4px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.ptz-top {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
.ptz-dpad {
|
|
||||||
position: relative;
|
|
||||||
width: 180px;
|
|
||||||
height: 180px;
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
.dpad-ring {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 130px;
|
|
||||||
height: 130px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #f5f7fa;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.dpad-btn {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
user-select: none;
|
|
||||||
transition: all 0.15s;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
.card:hover {
|
|
||||||
background: #409EFF;
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 3px 10px rgba(64,158,255,0.4);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
.card:active {
|
|
||||||
background: #337ecc;
|
|
||||||
transform: scale(0.92);
|
|
||||||
}
|
|
||||||
.card-up { top: 18px; left: 67px; }
|
|
||||||
.card-right { top: 67px; left: 116px; }
|
|
||||||
.card-down { top: 116px; left: 67px; }
|
|
||||||
.card-left { top: 67px; left: 18px; }
|
|
||||||
.diag {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #a8abb2;
|
|
||||||
}
|
|
||||||
.diag:hover {
|
|
||||||
background: #409EFF;
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 2px 8px rgba(64,158,255,0.35);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
.diag:active {
|
|
||||||
background: #337ecc;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
.diag-upright { top: 40px; left: 110px; }
|
|
||||||
.diag-downright { top: 110px; left: 110px; }
|
|
||||||
.diag-downleft { top: 110px; left: 34px; }
|
|
||||||
.diag-upleft { top: 40px; left: 34px; }
|
|
||||||
.dpad-center {
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background: linear-gradient(135deg, #eef0f4, #e0e3e8);
|
|
||||||
font-size: 20px;
|
|
||||||
color: #909399;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.dpad-center:hover {
|
|
||||||
background: #409EFF;
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 3px 10px rgba(64,158,255,0.4);
|
|
||||||
transform: translate(-50%, -50%) scale(1.1);
|
|
||||||
}
|
|
||||||
.dpad-center:active {
|
|
||||||
background: #337ecc;
|
|
||||||
transform: translate(-50%, -50%) scale(0.92);
|
|
||||||
}
|
|
||||||
.ptz-func-col {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.ptz-func-group {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.ptz-func-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.ptz-func-btn {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 44px;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #fff;
|
|
||||||
user-select: none;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.ptz-func-btn:hover {
|
|
||||||
background: #409EFF;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.ptz-func-btn:active {
|
|
||||||
background: #337ecc;
|
|
||||||
}
|
|
||||||
.ptz-func-btn i { font-size: 14px; margin-bottom: 2px; }
|
|
||||||
.ptz-func-group.row .ptz-func-btn {
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.ptz-func-group.row .ptz-func-btn i {
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
.ptz-bottom {
|
|
||||||
margin-top: 12px;
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
.slider-label {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.slider-btn {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
|
||||||
.slider-with-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.slider-with-controls .el-slider {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.slider-value {
|
|
||||||
min-width: 28px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
328
web/src/views/common/ptzCruising.vue
Normal file
328
web/src/views/common/ptzCruising.vue
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzCruising">
|
||||||
|
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||||
|
<span>巡航组号: </span>
|
||||||
|
<el-input
|
||||||
|
v-model="cruiseId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="巡航组号"
|
||||||
|
addon-before="巡航组号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<el-tag
|
||||||
|
v-for="(item, index) in presetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
closable
|
||||||
|
style="margin-right: 1rem; cursor: pointer"
|
||||||
|
@close="delPreset(item, index)"
|
||||||
|
>
|
||||||
|
{{ item.presetName ? item.presetName : item.presetId }}
|
||||||
|
</el-tag>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<el-form v-if="selectPresetVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="selectPreset" value-key="presetId" placeholder="请选择预置点">
|
||||||
|
<el-option
|
||||||
|
v-for="item in allPresetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
:label="item.presetName ? item.presetName : item.presetId"
|
||||||
|
:value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="addCruisePoint">保存</el-button>
|
||||||
|
<el-button type="primary" @click="cancelAddCruisePoint">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="selectPresetVisible=true">添加巡航点</el-button>
|
||||||
|
|
||||||
|
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-if="setSpeedVisible"
|
||||||
|
v-model="cruiseSpeed"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航速度"
|
||||||
|
addon-before="巡航速度"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setCruiseSpeed">保存</el-button>
|
||||||
|
<el-button @click="cancelSetCruiseSpeed">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setSpeedVisible = true">设置巡航速度</el-button>
|
||||||
|
<el-form v-if="setTimeVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="cruiseTime"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航停留时间(秒)"
|
||||||
|
addon-before="巡航停留时间(秒)"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setCruiseTime">保存</el-button>
|
||||||
|
<el-button @click="cancelSetCruiseTime">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setTimeVisible = true">设置巡航时间</el-button>
|
||||||
|
<el-button size="mini" @click="startCruise">开始巡航</el-button>
|
||||||
|
<el-button size="mini" @click="stopCruise">停止巡航</el-button>
|
||||||
|
<el-button size="mini" type="danger" @click="deleteCruise">删除巡航</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzCruising',
|
||||||
|
components: {},
|
||||||
|
props: ['channelDeviceId', 'deviceId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cruiseId: 1,
|
||||||
|
presetList: [],
|
||||||
|
allPresetList: [],
|
||||||
|
selectPreset: '',
|
||||||
|
inputVisible: false,
|
||||||
|
selectPresetVisible: false,
|
||||||
|
setSpeedVisible: false,
|
||||||
|
setTimeVisible: false,
|
||||||
|
cruiseSpeed: '',
|
||||||
|
cruiseTime: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getPresetList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPresetList: function() {
|
||||||
|
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||||
|
.then((data) => {
|
||||||
|
this.allPresetList = data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addCruisePoint: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/addPointForCruise',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId, this.selectPreset.presetId])
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList.push(this.selectPreset)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.selectPreset = ''
|
||||||
|
this.selectPresetVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelAddCruisePoint: function() {
|
||||||
|
this.selectPreset = ''
|
||||||
|
this.selectPresetVisible = false
|
||||||
|
},
|
||||||
|
delPreset: function(preset, index) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/deletePointForCruise',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId, preset.presetId])
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList.splice(index, 1)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteCruise: function(preset, index) {
|
||||||
|
this.$confirm('确定删除此巡航组', '提示', {
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/deletePointForCruise',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId, 0])
|
||||||
|
.then((data) => {
|
||||||
|
this.presetList = []
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setCruiseSpeed: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/setCruiseSpeed',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseSpeed])
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.cruiseSpeed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetCruiseSpeed: function() {
|
||||||
|
this.cruiseSpeed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
},
|
||||||
|
setCruiseTime: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/setCruiseTime',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId, this.cruiseTime])
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetCruiseTime: function() {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
},
|
||||||
|
startCruise: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/startCruise',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stopCruise: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/stopCruise',
|
||||||
|
[this.deviceId, this.channelDeviceId, this.cruiseId])
|
||||||
|
.then((data) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.setTimeVisible = false
|
||||||
|
this.cruiseTime = ''
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
152
web/src/views/common/ptzPreset.vue
Normal file
152
web/src/views/common/ptzPreset.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzPreset" style="width: 100%">
|
||||||
|
<el-tag
|
||||||
|
v-for="item in presetList"
|
||||||
|
:key="item.presetId"
|
||||||
|
closable
|
||||||
|
size="mini"
|
||||||
|
style="margin-right: 1rem; cursor: pointer; margin-bottom: 0.6rem"
|
||||||
|
@close="delPreset(item)"
|
||||||
|
@click="gotoPreset(item)"
|
||||||
|
>
|
||||||
|
{{ item.presetName?item.presetName:item.presetId }}
|
||||||
|
</el-tag>
|
||||||
|
<el-input
|
||||||
|
v-if="inputVisible"
|
||||||
|
ref="saveTagInput"
|
||||||
|
v-model="ptzPresetId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="预置位编号"
|
||||||
|
addon-before="预置位编号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
style="width: 300px; vertical-align: bottom;"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<el-button @click="addPreset()">保存</el-button>
|
||||||
|
<el-button @click="cancel()">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button v-else size="small" @click="showInput">+ 添加</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzPreset',
|
||||||
|
components: {},
|
||||||
|
props: ['channelDeviceId', 'deviceId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
presetList: [],
|
||||||
|
inputVisible: false,
|
||||||
|
ptzPresetId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getPresetList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPresetList: function() {
|
||||||
|
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
||||||
|
.then(data => {
|
||||||
|
this.presetList = data
|
||||||
|
// 防止出现表格错位
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.channelListTable.doLayout()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showInput() {
|
||||||
|
this.inputVisible = true
|
||||||
|
this.$nextTick(_ => {
|
||||||
|
this.$refs.saveTagInput.$refs.input.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addPreset: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/addPreset', [this.deviceId, this.channelDeviceId, this.ptzPresetId])
|
||||||
|
.then(data => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
this.getPresetList()
|
||||||
|
}, 1000)
|
||||||
|
}).catch((error) => {
|
||||||
|
loading.close()
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel: function() {
|
||||||
|
this.inputVisible = false
|
||||||
|
this.ptzPresetId = ''
|
||||||
|
},
|
||||||
|
gotoPreset: function(preset) {
|
||||||
|
console.log(preset)
|
||||||
|
this.$store.dispatch('frontEnd/callPreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '调用成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delPreset: function(preset) {
|
||||||
|
this.$confirm('确定删除此预置位', '提示', {
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
||||||
|
.then(data => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getPresetList()
|
||||||
|
}, 1000)
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
212
web/src/views/common/ptzScan.vue
Normal file
212
web/src/views/common/ptzScan.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzScan">
|
||||||
|
<div style="display: grid; grid-template-columns: 80px auto; line-height: 28px">
|
||||||
|
<span>扫描组号: </span>
|
||||||
|
<el-input
|
||||||
|
v-model="scanId"
|
||||||
|
min="1"
|
||||||
|
max="255"
|
||||||
|
placeholder="扫描组号"
|
||||||
|
addon-before="扫描组号"
|
||||||
|
addon-after="(1-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button size="mini" @click="setScanLeft">设置左边界</el-button>
|
||||||
|
<el-button size="mini" @click="setScanRight">设置右边界</el-button>
|
||||||
|
|
||||||
|
<el-form v-if="setSpeedVisible" size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-if="setSpeedVisible"
|
||||||
|
v-model="speed"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="巡航速度"
|
||||||
|
addon-before="巡航速度"
|
||||||
|
addon-after="(1-4095)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="setSpeed">保存</el-button>
|
||||||
|
<el-button @click="cancelSetSpeed">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button v-else size="mini" @click="setSpeedVisible = true">设置扫描速度</el-button>
|
||||||
|
|
||||||
|
<el-button size="mini" @click="startScan">开始自动扫描</el-button>
|
||||||
|
<el-button size="mini" @click="stopScan">停止自动扫描</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzScan',
|
||||||
|
components: {},
|
||||||
|
props: ['channelDeviceId', 'deviceId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
scanId: 1,
|
||||||
|
setSpeedVisible: false,
|
||||||
|
speed: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setSpeed: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/setSpeedForScan', [this.deviceId, this.channelDeviceId, this.scanId, this.speed])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelSetSpeed: function() {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
},
|
||||||
|
setScanLeft: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/setLeftForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setScanRight: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/setRightForScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.speed = ''
|
||||||
|
this.setSpeedVisible = false
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
startScan: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/startScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stopScan: function() {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/stopScan', [this.deviceId, this.channelDeviceId, this.scanId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
76
web/src/views/common/ptzSwitch.vue
Normal file
76
web/src/views/common/ptzSwitch.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzScan">
|
||||||
|
<el-form size="mini" :inline="true">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="switchId"
|
||||||
|
min="1"
|
||||||
|
max="4095"
|
||||||
|
placeholder="开关编号"
|
||||||
|
addon-before="开关编号"
|
||||||
|
addon-after="(2-255)"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||||
|
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzScan',
|
||||||
|
components: {},
|
||||||
|
props: ['channelDeviceId', 'deviceId'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
switchId: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open: function(command) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/auxiliary', [this.deviceId, this.channelDeviceId, command, this.switchId])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
58
web/src/views/common/ptzWiper.vue
Normal file
58
web/src/views/common/ptzWiper.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div id="ptzWiper">
|
||||||
|
<el-button size="mini" @click="open('on')">开启</el-button>
|
||||||
|
<el-button size="mini" @click="open('off')">关闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PtzWiper',
|
||||||
|
components: {},
|
||||||
|
props: ['channelDeviceId', 'deviceId'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open: function(command) {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
fullscreen: true,
|
||||||
|
text: '正在发送指令',
|
||||||
|
spinner: 'el-icon-loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('frontEnd/wiper', [this.deviceId, this.channelDeviceId, command])
|
||||||
|
.then(data => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '保存成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: error,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
loading.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.channel-form {
|
||||||
|
display: grid;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 1rem 2rem 0 2rem;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,17 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :id="'rtcPlayer-' + _uid" class="rtc-player-wrapper">
|
<div id="rtcPlayer">
|
||||||
<video :id="'webRtcPlayerBox-' + _uid" class="rtc-player-video" :controls="showControls" autoplay style="text-align:left;">
|
<video id="webRtcPlayerBox" :controls="showControls" autoplay style="text-align:left;">
|
||||||
Your browser is too old which doesn't support HTML5 video.
|
Your browser is too old which doesn't support HTML5 video.
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const webrtcPlayer = {}
|
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: '' },
|
||||||
@ -23,20 +21,20 @@ export default {
|
|||||||
timer: null
|
timer: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {},
|
mounted() {},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
clearTimeout(this.timer)
|
clearTimeout(this.timer)
|
||||||
this.pause()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
play: function(url) {
|
play: function(url) {
|
||||||
if (webrtcPlayer[this._uid]) {
|
if (webrtcPlayer != null) {
|
||||||
this.pause()
|
this.pause()
|
||||||
}
|
}
|
||||||
webrtcPlayer[this._uid] = new ZLMRTCClient.Endpoint({
|
webrtcPlayer = new ZLMRTCClient.Endpoint({
|
||||||
element: document.getElementById('webRtcPlayerBox-' + this._uid),
|
element: document.getElementById('webRtcPlayerBox'), // video 标签
|
||||||
debug: true,
|
debug: true, // 是否打印日志
|
||||||
zlmsdpUrl: url,
|
zlmsdpUrl: url, // 流地址
|
||||||
simulecast: false,
|
simulecast: false,
|
||||||
useCamera: false,
|
useCamera: false,
|
||||||
audioEnable: true,
|
audioEnable: true,
|
||||||
@ -44,37 +42,37 @@ export default {
|
|||||||
recvOnly: true,
|
recvOnly: true,
|
||||||
usedatachannel: false
|
usedatachannel: false
|
||||||
})
|
})
|
||||||
const player = webrtcPlayer[this._uid]
|
webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => { // ICE 协商出错
|
||||||
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {
|
|
||||||
console.error('ICE 协商出错')
|
console.error('ICE 协商出错')
|
||||||
this.eventcallbacK('ICE ERROR', 'ICE 协商出错')
|
this.eventcallbacK('ICE ERROR', 'ICE 协商出错')
|
||||||
})
|
})
|
||||||
|
|
||||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, (e) => {
|
webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, (e) => { // 获取到了远端流,可以播放
|
||||||
console.log('播放成功', e.streams)
|
console.log('播放成功', e.streams)
|
||||||
this.eventcallbacK('playing', '播放成功')
|
this.eventcallbacK('playing', '播放成功')
|
||||||
})
|
})
|
||||||
|
|
||||||
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {
|
webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => { // offer anwser 交换失败
|
||||||
console.error('offer anwser 交换失败', e)
|
console.error('offer anwser 交换失败', e)
|
||||||
this.eventcallbacK('OFFER ANSWER ERROR ', 'offer anwser 交换失败')
|
this.eventcallbacK('OFFER ANSWER ERROR ', 'offer anwser 交换失败')
|
||||||
if (e.code == -400 && e.msg == '流不存在') {
|
if (e.code == -400 && e.msg == '流不存在') {
|
||||||
console.log('流不存在')
|
console.log('流不存在')
|
||||||
this.timer = setTimeout(() => {
|
this.timer = setTimeout(() => {
|
||||||
player.close()
|
this.webrtcPlayer.close()
|
||||||
this.play(url)
|
this.play(url)
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM, (s) => {
|
webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM, (s) => { // 获取到了本地流
|
||||||
|
// document.getElementById('selfVideo').srcObject=s;
|
||||||
this.eventcallbacK('LOCAL STREAM', '获取到了本地流')
|
this.eventcallbacK('LOCAL STREAM', '获取到了本地流')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
pause: function() {
|
pause: function() {
|
||||||
if (webrtcPlayer[this._uid]) {
|
if (webrtcPlayer != null) {
|
||||||
webrtcPlayer[this._uid].close()
|
webrtcPlayer.close()
|
||||||
webrtcPlayer[this._uid] = null
|
webrtcPlayer = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stop: function() {
|
stop: function() {
|
||||||
@ -84,35 +82,6 @@ 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-' + this._uid)
|
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,12 +91,11 @@ export default {
|
|||||||
.LodingTitle {
|
.LodingTitle {
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
}
|
}
|
||||||
.rtc-player-wrapper{
|
#rtcPlayer{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.rtc-player-video{
|
#webRtcPlayerBox{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="media-info-content">
|
|
||||||
<el-form label-width="90px" size="small">
|
|
||||||
<el-form-item label="播放地址">
|
|
||||||
<el-input v-model="playerUrl" :disabled="true">
|
|
||||||
<template slot="append">
|
|
||||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" style="cursor: pointer" @click="copyUrl(playerUrl)" />
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="iframe">
|
|
||||||
<el-input v-model="sharedIframe" :disabled="true" >
|
|
||||||
<template slot="append">
|
|
||||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" style="cursor: pointer" @click="copyUrl(sharedIframe)" />
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="资源地址">
|
|
||||||
<el-input v-model="playUrl" :disabled="true" size="mini">
|
|
||||||
<el-button slot="append" icon="el-icon-document-copy" title="点击拷贝" style="cursor: pointer" @click="copyUrl(playUrl)" />
|
|
||||||
<el-dropdown v-if="streamInfo" slot="prepend" trigger="click" @command="copyUrl">
|
|
||||||
<el-button>更多地址<i class="el-icon-arrow-down el-icon--right" size="mini"/></el-button>
|
|
||||||
<el-dropdown-menu slot="dropdown">
|
|
||||||
<el-dropdown-item v-if="streamInfo.flv" :command="streamInfo.flv"><el-tag>FLV:</el-tag><span>{{ streamInfo.flv }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.https_flv" :command="streamInfo.https_flv"><el-tag>FLV(https):</el-tag><span>{{ streamInfo.https_flv }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.ws_flv" :command="streamInfo.ws_flv"><el-tag>FLV(ws):</el-tag><span>{{ streamInfo.ws_flv }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.wss_flv" :command="streamInfo.wss_flv"><el-tag>FLV(wss):</el-tag><span>{{ streamInfo.wss_flv }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.fmp4" :command="streamInfo.fmp4"><el-tag>FMP4:</el-tag><span>{{ streamInfo.fmp4 }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.https_fmp4" :command="streamInfo.https_fmp4"><el-tag>FMP4(https):</el-tag><span>{{ streamInfo.https_fmp4 }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.ws_fmp4" :command="streamInfo.ws_fmp4"><el-tag>FMP4(ws):</el-tag><span>{{ streamInfo.ws_fmp4 }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.wss_fmp4" :command="streamInfo.wss_fmp4"><el-tag>FMP4(wss):</el-tag><span>{{ streamInfo.wss_fmp4 }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.hls" :command="streamInfo.hls"><el-tag>HLS:</el-tag><span>{{ streamInfo.hls }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.https_hls" :command="streamInfo.https_hls"><el-tag>HLS(https):</el-tag><span>{{ streamInfo.https_hls }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.ws_hls" :command="streamInfo.ws_hls"><el-tag>HLS(ws):</el-tag><span>{{ streamInfo.ws_hls }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.wss_hls" :command="streamInfo.wss_hls"><el-tag>HLS(wss):</el-tag><span>{{ streamInfo.wss_hls }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.ts" :command="streamInfo.ts"><el-tag>TS:</el-tag><span>{{ streamInfo.ts }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.https_ts" :command="streamInfo.https_ts"><el-tag>TS(https):</el-tag><span>{{ streamInfo.https_ts }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.ws_ts" :command="streamInfo.ws_ts"><el-tag>TS(ws):</el-tag><span>{{ streamInfo.ws_ts }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.wss_ts" :command="streamInfo.wss_ts"><el-tag>TS(wss):</el-tag><span>{{ streamInfo.wss_ts }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtc" :command="streamInfo.rtc"><el-tag>RTC:</el-tag><span>{{ streamInfo.rtc }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtcs" :command="streamInfo.rtcs"><el-tag>RTCS:</el-tag><span>{{ streamInfo.rtcs }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtmp" :command="streamInfo.rtmp"><el-tag>RTMP:</el-tag><span>{{ streamInfo.rtmp }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtmps" :command="streamInfo.rtmps"><el-tag>RTMPS:</el-tag><span>{{ streamInfo.rtmps }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtsp" :command="streamInfo.rtsp"><el-tag>RTSP:</el-tag><span>{{ streamInfo.rtsp }}</span></el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="streamInfo.rtsps" :command="streamInfo.rtsps"><el-tag>RTSPS:</el-tag><span>{{ streamInfo.rtsps }}</span></el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</el-dropdown>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'StreamMediaPanel',
|
|
||||||
props: {
|
|
||||||
playerUrl: { type: String, default: '' },
|
|
||||||
playUrl: { type: String, default: '' },
|
|
||||||
streamInfo: { type: Object, default: null }
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sharedIframe() {
|
|
||||||
return `<iframe src="${this.playerUrl}"></iframe>`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copyUrl(text) {
|
|
||||||
this.$copyText(text).then(() => {
|
|
||||||
this.$message.success({ showClose: true, message: '成功拷贝到粘贴板' })
|
|
||||||
}, () => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import veHistogram from 'v-charts/lib/histogram'
|
import veHistogram from 'v-charts/lib/histogram'
|
||||||
import HasStreamChannel from "@/views/dashboard/dialog/hasStreamChannel.vue";
|
import HasStreamChannel from "@/views/dialog/hasStreamChannel";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ConsoleNodeLoad',
|
name: 'ConsoleNodeLoad',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="channelList" style="height: calc(100vh - 124px);">
|
<div id="channelList" style="height: calc(100vh - 124px);">
|
||||||
<div v-if="!editId && !ptzConfigChannelDeviceId" style="height: 100%">
|
<div v-if="!editId" style="height: 100%">
|
||||||
<el-form :inline="true" size="mini">
|
<el-form :inline="true" size="mini">
|
||||||
<el-form-item style="margin-right: 2rem">
|
<el-form-item style="margin-right: 2rem">
|
||||||
<el-page-header content="通道列表" @back="showDevice" />
|
<el-page-header content="通道列表" @back="showDevice" />
|
||||||
@ -189,10 +189,6 @@
|
|||||||
设备录像控制-开始</el-dropdown-item>
|
设备录像控制-开始</el-dropdown-item>
|
||||||
<el-dropdown-item command="stopRecord" :disabled="device == null || device.online === 0">
|
<el-dropdown-item command="stopRecord" :disabled="device == null || device.online === 0">
|
||||||
设备录像控制-停止</el-dropdown-item>
|
设备录像控制-停止</el-dropdown-item>
|
||||||
<el-dropdown-item command="ptzConfig" :disabled="device == null || device.online === 0">
|
|
||||||
云台配置</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>
|
||||||
@ -213,25 +209,19 @@
|
|||||||
|
|
||||||
<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" />
|
|
||||||
<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/channel/ptzConfig.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChannelList',
|
name: 'ChannelList',
|
||||||
components: {
|
components: {
|
||||||
devicePlayer,
|
devicePlayer,
|
||||||
audioTalk,
|
ChannelEdit: Edit
|
||||||
ChannelEdit: Edit,
|
|
||||||
ptzConfig
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
defaultPage: {
|
defaultPage: {
|
||||||
@ -268,8 +258,6 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
beforeUrl: '/device',
|
beforeUrl: '/device',
|
||||||
editId: null,
|
editId: null,
|
||||||
ptzConfigDeviceId: null,
|
|
||||||
ptzConfigChannelDeviceId: null,
|
|
||||||
loadSnap: {},
|
loadSnap: {},
|
||||||
ptzTypes: {
|
ptzTypes: {
|
||||||
0: '未知',
|
0: '未知',
|
||||||
@ -290,6 +278,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
console.log(23222)
|
||||||
if (this.deviceId) {
|
if (this.deviceId) {
|
||||||
this.$store.dispatch('device/queryDeviceOne', this.deviceId)
|
this.$store.dispatch('device/queryDeviceOne', this.deviceId)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -383,10 +372,6 @@ export default {
|
|||||||
itemData.playLoading = false
|
itemData.playLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
closePtzConfig: function() {
|
|
||||||
this.ptzConfigDeviceId = null
|
|
||||||
this.ptzConfigChannelDeviceId = null
|
|
||||||
},
|
|
||||||
moreClick: function(command, itemData) {
|
moreClick: function(command, itemData) {
|
||||||
if (command === 'records') {
|
if (command === 'records') {
|
||||||
this.queryRecords(itemData)
|
this.queryRecords(itemData)
|
||||||
@ -396,12 +381,6 @@ export default {
|
|||||||
this.startRecord(itemData)
|
this.startRecord(itemData)
|
||||||
} else if (command === 'stopRecord') {
|
} else if (command === 'stopRecord') {
|
||||||
this.stopRecord(itemData)
|
this.stopRecord(itemData)
|
||||||
} else if (command === 'ptzConfig') {
|
|
||||||
console.log(itemData.channelId)
|
|
||||||
this.ptzConfigDeviceId = this.deviceId
|
|
||||||
this.ptzConfigChannelDeviceId = itemData.deviceId
|
|
||||||
} else if (command === 'audioTalk') {
|
|
||||||
this.$refs.audioTalk.openDialog(this.deviceId, itemData.deviceId)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryRecords: function(itemData) {
|
queryRecords: function(itemData) {
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="dhPtzConfigPage">
|
|
||||||
<el-page-header content="云台设置" @back="$emit('close')" />
|
|
||||||
<div class="ptz-config-body">
|
|
||||||
<div class="config-sidebar">
|
|
||||||
<el-menu :default-active="activeTab" @select="handleMenuSelect">
|
|
||||||
<el-menu-item index="preset">
|
|
||||||
<i class="el-icon-map-location" style="margin-right: 6px" />
|
|
||||||
<span>预置点</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="cruise">
|
|
||||||
<i class="el-icon-s-order" style="margin-right: 6px" />
|
|
||||||
<span>巡航组</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="scan">
|
|
||||||
<i class="iconfont icon-slider-right" style="margin-right: 6px" />
|
|
||||||
<span>线性扫描</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="switch">
|
|
||||||
<i class="el-icon-s-tools" style="margin-right: 6px" />
|
|
||||||
<span>辅助开关</span>
|
|
||||||
</el-menu-item>
|
|
||||||
</el-menu>
|
|
||||||
</div>
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<div class="player-panel">
|
|
||||||
<playerPtzPanel :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
|
||||||
</div>
|
|
||||||
<div class="tab-panel">
|
|
||||||
<ptzPresetConfig v-if="activeTab === 'preset'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
|
||||||
<ptzCruiseConfig v-if="activeTab === 'cruise'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
|
||||||
<ptzScanConfig v-if="activeTab === 'scan'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
|
||||||
<ptzSwitchConfig v-if="activeTab === 'switch'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import playerPtzPanel from '../common/playerPtzPanel.vue'
|
|
||||||
import ptzPresetConfig from '../common/ptzPresetConfig.vue'
|
|
||||||
import ptzCruiseConfig from '../common/ptzCruiseConfig.vue'
|
|
||||||
import ptzScanConfig from '../common/ptzScanConfig.vue'
|
|
||||||
import ptzSwitchConfig from '../common/ptzSwitchConfig.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PtzConfigPage',
|
|
||||||
components: { playerPtzPanel, ptzPresetConfig, ptzCruiseConfig, ptzScanConfig, ptzSwitchConfig },
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeTab: 'preset'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleMenuSelect(index) {
|
|
||||||
this.activeTab = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#dhPtzConfigPage {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.ptz-config-body {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
.config-sidebar {
|
|
||||||
width: 140px;
|
|
||||||
flex: none;
|
|
||||||
border-right: 1px solid #e6e6e6;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.config-sidebar .el-menu {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
.content-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.player-panel {
|
|
||||||
width: 600px;
|
|
||||||
flex: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-right: 1px solid #e6e6e6;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
.tab-panel {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="ptz-section">
|
|
||||||
<ptzControls
|
|
||||||
btn-layout="row"
|
|
||||||
@ptz-move="onPtzMove"
|
|
||||||
@ptz-stop="onPtzStop"
|
|
||||||
@focus-move="onFocusMove"
|
|
||||||
@focus-stop="onFocusStop"
|
|
||||||
@iris-move="onIrisMove"
|
|
||||||
@iris-stop="onIrisStop"
|
|
||||||
@toggle-drag-zoom="$emit('drag-zoom-start', 'in')"
|
|
||||||
@toggle-drag-zoom-out="$emit('drag-zoom-start', 'out')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ptzControls from '../../common/ptzControls.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'DevicePtzPanel',
|
|
||||||
components: { ptzControls },
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
ptzSpeed(speed) {
|
|
||||||
return parseInt(speed * 255 / 100)
|
|
||||||
},
|
|
||||||
onPtzMove(e) {
|
|
||||||
const speedVal = this.ptzSpeed(e.speed)
|
|
||||||
this.$store.dispatch('frontEnd/ptz', {
|
|
||||||
deviceId: this.deviceId,
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: e.direction,
|
|
||||||
horizonSpeed: speedVal,
|
|
||||||
verticalSpeed: speedVal,
|
|
||||||
zoomSpeed: parseInt(e.speed * 15 / 100)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onPtzStop() {
|
|
||||||
this.$store.dispatch('frontEnd/ptz', {
|
|
||||||
deviceId: this.deviceId,
|
|
||||||
channelId: this.channelId,
|
|
||||||
command: 'stop',
|
|
||||||
horizonSpeed: 0,
|
|
||||||
verticalSpeed: 0,
|
|
||||||
zoomSpeed: 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onFocusMove(e) {
|
|
||||||
const speedVal = this.ptzSpeed(e.speed)
|
|
||||||
this.$store.dispatch('frontEnd/focus', [this.deviceId, this.channelId, e.command, speedVal])
|
|
||||||
},
|
|
||||||
onFocusStop() {
|
|
||||||
this.$store.dispatch('frontEnd/focus', [this.deviceId, this.channelId, 'stop', 0])
|
|
||||||
},
|
|
||||||
onIrisMove(e) {
|
|
||||||
const speedVal = this.ptzSpeed(e.speed)
|
|
||||||
this.$store.dispatch('frontEnd/iris', [this.deviceId, this.channelId, e.command, speedVal])
|
|
||||||
},
|
|
||||||
onIrisStop() {
|
|
||||||
this.$store.dispatch('frontEnd/iris', [this.deviceId, this.channelId, 'stop', 0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.ptz-section {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-ptz-panel">
|
|
||||||
<div class="player-section">
|
|
||||||
<div class="player-wrapper" :style="{ height: playerHeight }">
|
|
||||||
<playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<devicePtzPanel
|
|
||||||
style="margin-top: 5vh"
|
|
||||||
:device-id="deviceId"
|
|
||||||
:channel-id="channelDeviceId"
|
|
||||||
@drag-zoom-start="toggleDragZoom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import playerTabs from '../../common/playerTabs.vue'
|
|
||||||
import devicePtzPanel from './devicePtzPanel.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PlayerPtzPanel',
|
|
||||||
components: { playerTabs, devicePtzPanel },
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
hasAudio: false,
|
|
||||||
playerHeight: '40vh',
|
|
||||||
dragZoomDirection: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.startPlay()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.stopPlay()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
startPlay() {
|
|
||||||
this.$store.dispatch('play/play', [this.deviceId, this.channelDeviceId])
|
|
||||||
.then(data => {
|
|
||||||
this.hasAudio = data.hasAudio
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.setStreamInfo(data.transcodeStream || data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
this.$message({ showClose: true, message: e || '播放失败', type: 'error' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopPlay() {
|
|
||||||
this.$store.dispatch('play/stop', { deviceId: this.deviceId, channelId: this.channelDeviceId })
|
|
||||||
.catch(() => {})
|
|
||||||
},
|
|
||||||
toggleDragZoom(direction) {
|
|
||||||
this.dragZoomDirection = direction
|
|
||||||
this.$refs.playerTabs.startDragZoom((params) => {
|
|
||||||
params.deviceId = this.deviceId
|
|
||||||
params.channelId = this.channelDeviceId
|
|
||||||
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 = ''
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.player-ptz-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.player-section {
|
|
||||||
flex: 0.8;
|
|
||||||
}
|
|
||||||
.player-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,267 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzCruiseConfig" style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :disabled="formVisible" @click="openAdd">添加巡航组</el-button>
|
|
||||||
<el-button :loading="clearing" :disabled="clearing" @click="clearCruiseTours">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle @click="loadPresets" />
|
|
||||||
</div>
|
|
||||||
<div v-if="formVisible" style="margin-bottom: 6px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px;">
|
|
||||||
<el-form inline size="small" style="display: flex; align-items: center; margin-top: 15px;">
|
|
||||||
<el-form-item label="序号" style="margin-bottom: 0;">
|
|
||||||
<el-input-number v-model="formId" :min="0" :max="255" controls-position="right" style="width: 120px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="名称" style="margin-bottom: 0;">
|
|
||||||
<el-input v-model="formName" placeholder="名称" style="width: 140px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmSave">确定</el-button>
|
|
||||||
<el-button @click="cancelForm">取消</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-divider style="margin: 6px 0;" />
|
|
||||||
<div style="margin-bottom: 4px;">
|
|
||||||
<el-button size="mini" type="primary" @click="addPresetRow">添加预置点</el-button>
|
|
||||||
</div>
|
|
||||||
<el-table :data="formPresets" size="mini" stripe border max-height="200px">
|
|
||||||
<el-table-column label="序号" width="50">
|
|
||||||
<template v-slot="{ $index }">{{ $index + 1 }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="预置点" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.presetId" size="mini" style="width: 120px" placeholder="选择预置点">
|
|
||||||
<el-option v-for="p in allPresetList" :key="p.presetId"
|
|
||||||
:label="p.presetName || ('预置点' + p.presetId)"
|
|
||||||
:value="p.presetId" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="停留时间(秒)" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-input-number v-model="row.dwellTime" :min="15" :max="300" size="mini" controls-position="right" style="width: 90px" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="速度" min-width="100">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.speed" size="mini" style="width: 90px">
|
|
||||||
<el-option v-for="s in 10" :key="s" :label="s" :value="s" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="60">
|
|
||||||
<template v-slot="{ $index }">
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" @click="removePresetRow($index)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-if="cruiseTours.length > 0" style="flex: 1; overflow: auto;">
|
|
||||||
<el-table ref="cruiseTable" :data="cruiseTours" size="mini" max-height="100%" stripe border highlight-current-row>
|
|
||||||
<el-table-column prop="id" label="ID" />
|
|
||||||
<el-table-column prop="name" label="巡航名称" />
|
|
||||||
<el-table-column label="操作" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<el-button v-if="cruisingCruiseId === scope.row.id" size="mini" type="text" style="color: #F56C6C" :loading="operatingId === scope.row.id" :disabled="operatingId !== null" @click="stopCruise(scope.row)">停用</el-button>
|
|
||||||
<el-button v-else size="mini" type="text" :disabled="cruisingCruiseId !== null || operatingId !== null" style="color: #409EFF" :loading="operatingId === scope.row.id" @click="startCruise(scope.row)">启用</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #409EFF" :disabled="operatingId !== null" @click="openEdit(scope.row)">编辑</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" :loading="deletingId === scope.row.id" :disabled="operatingId !== null || deletingId !== null" @click="deleteCruise(scope.row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else style="color: #909399; font-size: 12px; margin-bottom: 8px;">暂无巡航路线</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzCruiseConfig',
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
cruiseTours: [],
|
|
||||||
cruisingCruiseId: null,
|
|
||||||
formVisible: false,
|
|
||||||
editingTourId: null,
|
|
||||||
submitting: false,
|
|
||||||
clearing: false,
|
|
||||||
operatingId: null,
|
|
||||||
deletingId: null,
|
|
||||||
formId: 1,
|
|
||||||
formName: '',
|
|
||||||
formPresets: [],
|
|
||||||
allPresetList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadPresets()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadPresets() {
|
|
||||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
|
||||||
.then(data => {
|
|
||||||
this.allPresetList = data || []
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('[巡航] 加载预置点列表失败', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getNextAvailableId() {
|
|
||||||
const used = new Set((this.cruiseTours || []).map(t => t.id))
|
|
||||||
for (let i = 0; i <= 255; i++) {
|
|
||||||
if (!used.has(i)) return i
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
openAdd() {
|
|
||||||
this.editingTourId = null
|
|
||||||
this.formId = this.getNextAvailableId()
|
|
||||||
this.formName = '巡航组' + this.formId
|
|
||||||
this.formPresets = []
|
|
||||||
this.formVisible = true
|
|
||||||
},
|
|
||||||
openEdit(tour) {
|
|
||||||
this.editingTourId = tour.id
|
|
||||||
this.formId = tour.id
|
|
||||||
this.formName = tour.name
|
|
||||||
this.formPresets = (tour.presets || []).map(p => ({
|
|
||||||
presetId: p.presetId,
|
|
||||||
dwellTime: p.dwellTime,
|
|
||||||
speed: p.speed
|
|
||||||
}))
|
|
||||||
if (this.formPresets.length === 0) {
|
|
||||||
this.formPresets.push({ presetId: this.getFirstPresetId(), dwellTime: 15, speed: 7 })
|
|
||||||
}
|
|
||||||
this.formVisible = true
|
|
||||||
},
|
|
||||||
cancelForm() {
|
|
||||||
this.formVisible = false
|
|
||||||
this.editingTourId = null
|
|
||||||
this.formPresets = []
|
|
||||||
},
|
|
||||||
getFirstPresetId() {
|
|
||||||
const first = this.allPresetList[0]
|
|
||||||
return first ? first.presetId : 1
|
|
||||||
},
|
|
||||||
addPresetRow() {
|
|
||||||
this.formPresets.push({
|
|
||||||
presetId: this.getFirstPresetId(),
|
|
||||||
dwellTime: 15,
|
|
||||||
speed: 7
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removePresetRow(index) {
|
|
||||||
this.formPresets.splice(index, 1)
|
|
||||||
},
|
|
||||||
confirmSave() {
|
|
||||||
if (!this.formName.trim()) {
|
|
||||||
this.$message({ showClose: true, message: '请输入巡航组名称', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.formId == null || this.formId < 0 || this.formId > 255) {
|
|
||||||
this.$message({ showClose: true, message: '巡航序号必须在0-255之间', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.submitting = true
|
|
||||||
let chain = Promise.resolve()
|
|
||||||
if (this.editingTourId !== null) {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('frontEnd/deletePointForCruise', [this.deviceId, this.channelDeviceId, this.formId, 0]))
|
|
||||||
}
|
|
||||||
this.formPresets.forEach(p => {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('frontEnd/addPointForCruise', [this.deviceId, this.channelDeviceId, this.formId, p.presetId]))
|
|
||||||
})
|
|
||||||
const speed = this.formPresets.length > 0 ? this.formPresets[0].speed : 7
|
|
||||||
const dwellTime = this.formPresets.length > 0 ? this.formPresets[0].dwellTime : 15
|
|
||||||
chain = chain.then(() => this.$store.dispatch('frontEnd/setCruiseSpeed', [this.deviceId, this.channelDeviceId, this.formId, speed]))
|
|
||||||
chain = chain.then(() => this.$store.dispatch('frontEnd/setCruiseTime', [this.deviceId, this.channelDeviceId, this.formId, dwellTime]))
|
|
||||||
chain.then(() => {
|
|
||||||
const idx = this.cruiseTours.findIndex(t => t.id === this.formId)
|
|
||||||
const presets = this.formPresets.map(p => ({
|
|
||||||
presetId: p.presetId,
|
|
||||||
dwellTime: p.dwellTime,
|
|
||||||
speed: p.speed
|
|
||||||
}))
|
|
||||||
const tour = { id: this.formId, name: this.formName, presets }
|
|
||||||
if (idx !== -1) {
|
|
||||||
this.$set(this.cruiseTours, idx, tour)
|
|
||||||
} else {
|
|
||||||
this.cruiseTours.push(tour)
|
|
||||||
}
|
|
||||||
this.cancelForm()
|
|
||||||
this.$message({ showClose: true, message: '保存成功', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error || '保存失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.submitting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearCruiseTours() {
|
|
||||||
if (this.cruiseTours.length === 0) return
|
|
||||||
this.$confirm('确定清空所有巡航组?', '提示', {
|
|
||||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.clearing = true
|
|
||||||
let chain = Promise.resolve()
|
|
||||||
this.cruiseTours.forEach(tour => {
|
|
||||||
chain = chain.then(() => this.$store.dispatch('frontEnd/deletePointForCruise', [this.deviceId, this.channelDeviceId, tour.id, 0]))
|
|
||||||
})
|
|
||||||
chain.then(() => {
|
|
||||||
this.cruiseTours = []
|
|
||||||
this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '清空成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '清空失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.clearing = false
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
startCruise(row) {
|
|
||||||
this.operatingId = row.id
|
|
||||||
this.$store.dispatch('frontEnd/startCruise', [this.deviceId, this.channelDeviceId, row.id])
|
|
||||||
.then(() => {
|
|
||||||
this.cruisingCruiseId = row.id
|
|
||||||
this.$message({ showClose: true, message: '启用成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopCruise(row) {
|
|
||||||
this.operatingId = row.id
|
|
||||||
this.$store.dispatch('frontEnd/stopCruise', [this.deviceId, this.channelDeviceId, row.id])
|
|
||||||
.then(() => {
|
|
||||||
this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '停止成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '停止失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteCruise(row) {
|
|
||||||
this.$confirm('确定删除此巡航组?', '提示', {
|
|
||||||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.deletingId = row.id
|
|
||||||
this.$store.dispatch('frontEnd/deletePointForCruise', [this.deviceId, this.channelDeviceId, row.id, 0])
|
|
||||||
.then(() => {
|
|
||||||
const idx = this.cruiseTours.indexOf(row)
|
|
||||||
if (idx !== -1) this.cruiseTours.splice(idx, 1)
|
|
||||||
if (this.cruisingCruiseId === row.id) this.cruisingCruiseId = null
|
|
||||||
this.$message({ showClose: true, message: '删除成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '删除失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.deletingId = null
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,156 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :disabled="showAddForm" @click="openAdd">添加预置点</el-button>
|
|
||||||
<el-button :loading="clearing" :disabled="clearing" @click="clearAll">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle @click="getPresetList" />
|
|
||||||
</div>
|
|
||||||
<el-form v-if="showAddForm" size="small" inline style="margin-bottom: 6px; padding: 16px 8px; border: 1px solid #e6e6e6; border-radius: 4px; display: flex; align-items: center;">
|
|
||||||
<el-form-item label="序号" style="margin-bottom: 0; margin-right: 2rem">
|
|
||||||
<el-input-number v-model="addPresetId" :min="1" :max="255" controls-position="right" style="width: 180px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmAdd">确定</el-button>
|
|
||||||
<el-button @click="cancelAdd">取消</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-table
|
|
||||||
:data="presetList"
|
|
||||||
border
|
|
||||||
stripe
|
|
||||||
max-height="100%"
|
|
||||||
style="flex: 1"
|
|
||||||
>
|
|
||||||
<el-table-column prop="presetId" label="序号" align="center" />
|
|
||||||
<el-table-column label="名称">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<span>{{ row.presetName || ('预置点' + row.presetId) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" min-width="140" align="center">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button size="mini" type="text" @click="callPreset(row)">调用</el-button>
|
|
||||||
<el-button size="mini" type="text" style="color: #F56C6C" :loading="deletingId === row.presetId" :disabled="deletingId !== null" @click="delPreset(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzPresetConfig',
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
presetList: [],
|
|
||||||
showAddForm: false,
|
|
||||||
addPresetId: 1,
|
|
||||||
submitting: false,
|
|
||||||
clearing: false,
|
|
||||||
deletingId: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getPresetList()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPresetList() {
|
|
||||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
|
||||||
.then(data => {
|
|
||||||
this.presetList = data || []
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openAdd() {
|
|
||||||
this.addPresetId = this.getNextAvailableId()
|
|
||||||
this.showAddForm = true
|
|
||||||
},
|
|
||||||
cancelAdd() {
|
|
||||||
this.showAddForm = false
|
|
||||||
this.addPresetId = 1
|
|
||||||
},
|
|
||||||
confirmAdd() {
|
|
||||||
const exists = this.presetList.some(p => p.presetId === this.addPresetId)
|
|
||||||
if (exists) {
|
|
||||||
this.$message({ showClose: true, message: '序号 ' + this.addPresetId + ' 已存在', type: 'warning' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.submitting = true
|
|
||||||
this.$store.dispatch('frontEnd/addPreset', [this.deviceId, this.channelDeviceId, this.addPresetId])
|
|
||||||
.then(() => {
|
|
||||||
this.showAddForm = false
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getPresetList()
|
|
||||||
}, 600)
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.submitting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
callPreset(preset) {
|
|
||||||
this.$store.dispatch('frontEnd/callPreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '调用成功', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
delPreset(preset) {
|
|
||||||
this.$confirm('确定删除此预置位', '提示', {
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.deletingId = preset.presetId
|
|
||||||
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
|
||||||
.then(() => {
|
|
||||||
this.getPresetList()
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.deletingId = null
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
clearAll() {
|
|
||||||
if (this.presetList.length === 0) return
|
|
||||||
this.$confirm('确定清空所有预置点?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.clearing = true
|
|
||||||
const promises = this.presetList.map(p =>
|
|
||||||
this.$store.dispatch('frontEnd/deletePreset', [this.deviceId, this.channelDeviceId, p.presetId])
|
|
||||||
)
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
this.presetList = []
|
|
||||||
this.$message({ showClose: true, message: '清空成功', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '清空失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.clearing = false
|
|
||||||
})
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
getNextAvailableId() {
|
|
||||||
if (!this.presetList || this.presetList.length === 0) return 1
|
|
||||||
const used = this.presetList.map(p => Number(p.presetId)).sort((a, b) => a - b)
|
|
||||||
for (let i = 0; i < used.length - 1; i++) {
|
|
||||||
if (used[i + 1] - used[i] > 1) return used[i] + 1
|
|
||||||
}
|
|
||||||
return used[used.length - 1] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzScanConfig" style="height: 100%; display: flex; flex-direction: column;">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
|
||||||
<div>
|
|
||||||
<el-button type="primary" :loading="adding" :disabled="adding" @click="addLineScan">添加线扫</el-button>
|
|
||||||
<el-button @click="clearAll">清空</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button icon="el-icon-refresh-right" circle />
|
|
||||||
</div>
|
|
||||||
<div v-if="scanAreas.length > 0" style="flex: 1; overflow: auto;">
|
|
||||||
<el-table :data="scanAreas" max-height="100%" stripe border highlight-current-row height="100%">
|
|
||||||
<el-table-column label="序号" min-width="50">
|
|
||||||
<template v-slot="{ row }">{{ row.index }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="名称" min-width="80">
|
|
||||||
<template v-slot="{ row }">{{ row.name }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="左边界" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button type="text"
|
|
||||||
:style="{ color: row.leftBoundary ? '#67C23A' : '#409EFF' }"
|
|
||||||
:loading="boundaryLoading.index === row.index && boundaryLoading.side === 'Left'"
|
|
||||||
:disabled="operatingId !== null"
|
|
||||||
@click="setBoundary(row, 'Left')">
|
|
||||||
{{ row.leftBoundary ? '重新保存' : '待保存' }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="右边界" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-button type="text"
|
|
||||||
:style="{ color: row.rightBoundary ? '#67C23A' : '#409EFF' }"
|
|
||||||
:loading="boundaryLoading.index === row.index && boundaryLoading.side === 'Right'"
|
|
||||||
:disabled="operatingId !== null"
|
|
||||||
@click="setBoundary(row, 'Right')">
|
|
||||||
{{ row.rightBoundary ? '重新保存' : '待保存' }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="速度" min-width="90">
|
|
||||||
<template v-slot="{ row }">
|
|
||||||
<el-select v-model="row.speed" :disabled="speedSaving === row.index" @change="onSpeedChange(row)">
|
|
||||||
<el-option v-for="s in 8" :key="s" :label="s" :value="s" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" min-width="120">
|
|
||||||
<template v-slot="{ row, $index }">
|
|
||||||
<el-button v-if="$index === cruisingScanIndex" type="text" style="color: #F56C6C" :loading="operatingId === row.index" :disabled="operatingId !== null" @click="stopScan(row)">停用</el-button>
|
|
||||||
<el-button v-else type="text" style="color: #409EFF" :disabled="operatingId !== null" :loading="operatingId === row.index" @click="startScan(row, $index)">启用</el-button>
|
|
||||||
<el-button type="text" style="color: #F56C6C" :disabled="operatingId !== null" @click="deleteScan(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else style="color: #909399; font-size: 12px; margin-bottom: 8px;">暂无线扫区域</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzScanConfig',
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
scanAreas: [],
|
|
||||||
cruisingScanIndex: null,
|
|
||||||
operatingId: null,
|
|
||||||
adding: false,
|
|
||||||
boundaryLoading: { index: null, side: null },
|
|
||||||
speedSaving: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getNextAvailableIndex() {
|
|
||||||
const used = new Set(this.scanAreas.filter(a => a.name && a.name.trim()).map(a => a.index))
|
|
||||||
for (let i = 0; i <= 255; i++) {
|
|
||||||
if (!used.has(i)) return i
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
addLineScan() {
|
|
||||||
const nextIndex = this.getNextAvailableIndex()
|
|
||||||
const name = '线扫' + nextIndex
|
|
||||||
this.adding = true
|
|
||||||
this.scanAreas.push({
|
|
||||||
index: nextIndex,
|
|
||||||
name: name,
|
|
||||||
leftBoundary: false,
|
|
||||||
rightBoundary: false,
|
|
||||||
speed: 5
|
|
||||||
})
|
|
||||||
this.$nextTick(() => { this.adding = false })
|
|
||||||
},
|
|
||||||
setBoundary(row, boundary) {
|
|
||||||
this.boundaryLoading = { index: row.index, side: boundary }
|
|
||||||
const action = boundary === 'Left' ? 'setLeftForScan' : 'setRightForScan'
|
|
||||||
this.$store.dispatch('frontEnd/' + action, [this.deviceId, this.channelDeviceId, row.index])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: (boundary === 'Left' ? '左' : '右') + '边界设置成功', type: 'success' })
|
|
||||||
if (boundary === 'Left') {
|
|
||||||
row.leftBoundary = true
|
|
||||||
} else {
|
|
||||||
row.rightBoundary = true
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '边界设置失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.boundaryLoading = { index: null, side: null }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onSpeedChange(row) {
|
|
||||||
this.speedSaving = row.index
|
|
||||||
this.$store.dispatch('frontEnd/setSpeedForScan', [this.deviceId, this.channelDeviceId, row.index, row.speed])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '速度已保存', type: 'success' })
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '速度保存失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.speedSaving = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
startScan(row, index) {
|
|
||||||
this.operatingId = row.index
|
|
||||||
this.$store.dispatch('frontEnd/startScan', [this.deviceId, this.channelDeviceId, row.index])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用成功', type: 'success' })
|
|
||||||
this.cruisingScanIndex = index
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '启用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopScan(row) {
|
|
||||||
this.operatingId = row.index
|
|
||||||
this.$store.dispatch('frontEnd/stopScan', [this.deviceId, this.channelDeviceId, row.index])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: '停用成功', type: 'success' })
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message({ showClose: true, message: '停用失败', type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.operatingId = null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteScan(row) {
|
|
||||||
this.$confirm('确定删除线扫 ' + row.index + '?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
const idx = this.scanAreas.indexOf(row)
|
|
||||||
if (idx !== -1) this.scanAreas.splice(idx, 1)
|
|
||||||
if (this.cruisingScanIndex !== null && this.scanAreas[this.cruisingScanIndex] === undefined) {
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
}
|
|
||||||
this.$message({ showClose: true, message: '删除成功(仅本地列表,设备端配置需手动清除)', type: 'success' })
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
clearAll() {
|
|
||||||
if (this.scanAreas.length === 0) return
|
|
||||||
this.$confirm('确定清空所有线扫区域?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
this.scanAreas = []
|
|
||||||
this.cruisingScanIndex = null
|
|
||||||
this.$message({ showClose: true, message: '清空成功(仅本地列表,设备端配置需手动清除)', type: 'success' })
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-form inline label-width="120px" size="small">
|
|
||||||
<el-form-item label="开关编号" style="margin-bottom: 0;">
|
|
||||||
<el-input-number v-model="switchId" :min="1" :max="255" controls-position="right" style="width: 140px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="margin-bottom: 0;">
|
|
||||||
<el-button type="primary" :loading="loading" :disabled="loading" @click="control('on')">开启</el-button>
|
|
||||||
<el-button :loading="loading" :disabled="loading" @click="control('off')">关闭</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
<el-divider />
|
|
||||||
|
|
||||||
<el-form-item style="margin-bottom: 0;" label="雨刷">
|
|
||||||
<el-button type="primary" :loading="wiperLoading" :disabled="wiperLoading" @click="wiperControl('on')">开启</el-button>
|
|
||||||
<el-button :loading="wiperLoading" :disabled="wiperLoading" @click="wiperControl('off')">关闭</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzSwitchConfig',
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
switchId: 1,
|
|
||||||
loading: false,
|
|
||||||
wiperLoading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
wiperControl(command) {
|
|
||||||
this.wiperLoading = true
|
|
||||||
this.$store.dispatch('frontEnd/wiper', [this.deviceId, this.channelDeviceId, command])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: command === 'on' ? '雨刷已开启' : '雨刷已关闭', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.wiperLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
control(command) {
|
|
||||||
this.loading = true
|
|
||||||
this.$store.dispatch('frontEnd/auxiliary', [this.deviceId, this.channelDeviceId, command, this.switchId])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: command === 'on' ? '开关已开启' : '开关已关闭', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('on')">开启</el-button>
|
|
||||||
<el-button size="small" :loading="loading" :disabled="loading" @click="control('off')">关闭</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzWiperConfig',
|
|
||||||
props: {
|
|
||||||
deviceId: { type: String, default: null },
|
|
||||||
channelDeviceId: { type: String, default: null }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
control(command) {
|
|
||||||
this.loading = true
|
|
||||||
this.$store.dispatch('frontEnd/wiper', [this.deviceId, this.channelDeviceId, command])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({ showClose: true, message: command === 'on' ? '雨刷已开启' : '雨刷已关闭', type: 'success' })
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message({ showClose: true, message: error, type: 'error' })
|
|
||||||
}).finally(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,373 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-dialog
|
|
||||||
title="语音对讲"
|
|
||||||
top="10vh"
|
|
||||||
width="61.5vw"
|
|
||||||
: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="true">喊话</el-radio-button>
|
|
||||||
<el-radio-button :label="false">对讲</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 && talkMode">喊话中</span>
|
|
||||||
<span v-if="talkStatus === 1 && !talkMode && !playConnected">等待接通中...</span>
|
|
||||||
<span v-if="talkStatus === 1 && !talkMode && playConnected">对讲中</span>
|
|
||||||
</p>
|
|
||||||
<p v-if="talkStatus === 1 && !talkMode && talkAudioFailed" style="margin-top: 8px;">
|
|
||||||
<el-button
|
|
||||||
type="warning"
|
|
||||||
size="mini"
|
|
||||||
icon="el-icon-refresh"
|
|
||||||
@click="retryTalkAudio"
|
|
||||||
>重试音频</el-button>
|
|
||||||
</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: true,
|
|
||||||
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.transcodeStream || 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) {
|
|
||||||
if (!this.talkMode && !this.playConnected) return 'warning'
|
|
||||||
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)
|
|
||||||
|
|
||||||
const playStreamInfo = data?.playStreamInfo
|
|
||||||
if (!this.talkMode && playStreamInfo) {
|
|
||||||
this.talkAudioPlayStream = playStreamInfo
|
|
||||||
this.startTalkAudioPlay(playStreamInfo)
|
|
||||||
this.muteVideoPlayer()
|
|
||||||
}
|
|
||||||
} 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 })
|
|
||||||
},
|
|
||||||
muteVideoPlayer() {
|
|
||||||
const player = this.$refs.playerTabs
|
|
||||||
if (!player) return
|
|
||||||
if (player.mute) {
|
|
||||||
player.mute()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmuteVideoPlayer() {
|
|
||||||
const player = this.$refs.playerTabs
|
|
||||||
if (!player) return
|
|
||||||
if (player.cancelMute) {
|
|
||||||
player.cancelMute()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startTalkAudioPlay(playStreamInfo) {
|
|
||||||
if (this.talkAudioRtc) {
|
|
||||||
this.talkAudioRtc.close()
|
|
||||||
}
|
|
||||||
if (this.talkAudioRetryTimer) {
|
|
||||||
clearTimeout(this.talkAudioRetryTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = location.protocol === 'https:' ? playStreamInfo.rtcs : playStreamInfo.rtc
|
|
||||||
if (!url) {
|
|
||||||
console.warn('[AudioTalk] 无可用的设备音频播放地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.pollMediaInfoAndPlay(playStreamInfo)
|
|
||||||
}, 800)
|
|
||||||
},
|
|
||||||
async pollMediaInfoAndPlay(playStreamInfo) {
|
|
||||||
try {
|
|
||||||
const data = await this.$store.dispatch('server/getMediaInfo', {
|
|
||||||
app: playStreamInfo.app,
|
|
||||||
stream: playStreamInfo.stream,
|
|
||||||
mediaServerId: playStreamInfo.mediaServerId
|
|
||||||
})
|
|
||||||
if (data) {
|
|
||||||
const url = location.protocol === 'https:' ? playStreamInfo.rtcs : playStreamInfo.rtc
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
} else {
|
|
||||||
throw new Error('no data')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (this.talkStatus === 1 || this.talkStatus === 0) {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.pollMediaInfoAndPlay(playStreamInfo)
|
|
||||||
}, 800)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startTalkAudioByRtc(url) {
|
|
||||||
this.talkAudioFailed = false
|
|
||||||
this.talkAudioRtc = new ZLMRTCClient.Endpoint({
|
|
||||||
debug: false,
|
|
||||||
element: document.getElementById('audioTalkVideo'),
|
|
||||||
zlmsdpUrl: url,
|
|
||||||
simulecast: false,
|
|
||||||
useCamera: false,
|
|
||||||
audioEnable: true,
|
|
||||||
videoEnable: false,
|
|
||||||
recvOnly: true,
|
|
||||||
usedatachannel: false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {
|
|
||||||
console.warn('[AudioTalk] 播放流offer失败:', e?.code, e?.msg)
|
|
||||||
if (e && e.code == -400 && e.msg == '流不存在') {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, () => {
|
|
||||||
console.warn('[AudioTalk] 设备音频流到达')
|
|
||||||
this.playConnected = true
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, () => {
|
|
||||||
console.error('[AudioTalk] 音频播放ICE协商失败')
|
|
||||||
})
|
|
||||||
|
|
||||||
this.talkAudioRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (s) => {
|
|
||||||
console.warn('[AudioTalk] 音频播放连接状态:', s)
|
|
||||||
if (s === 'connected') {
|
|
||||||
this.playConnected = true
|
|
||||||
} else if (s === 'disconnected' || s === 'failed' || s === 'closed') {
|
|
||||||
this.playConnected = false
|
|
||||||
this.talkAudioFailed = true
|
|
||||||
if (this.talkStatus === 1) {
|
|
||||||
this.talkAudioRetryTimer = setTimeout(() => {
|
|
||||||
this.startTalkAudioByRtc(url)
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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
|
|
||||||
this.unmuteVideoPlayer()
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('play/broadcastStop', [this.deviceId, this.channelId])
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('停止对讲失败', e)
|
|
||||||
}
|
|
||||||
this.talkStatus = -1
|
|
||||||
},
|
|
||||||
retryTalkAudio() {
|
|
||||||
if (this.talkAudioPlayStream) {
|
|
||||||
this.startTalkAudioPlay(this.talkAudioPlayStream)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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>
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="devicePlayer" v-loading="isLoging">
|
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-if="showVideoDialog"
|
|
||||||
v-el-drag-dialog
|
|
||||||
title="视频播放"
|
|
||||||
top="10vh"
|
|
||||||
width="65vw"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:visible.sync="showVideoDialog"
|
|
||||||
@close="close()"
|
|
||||||
>
|
|
||||||
<div class="dhsdk-player-body">
|
|
||||||
|
|
||||||
<div class="player-side">
|
|
||||||
<div class="player-container" :style="{ height: playerHeight }">
|
|
||||||
<playerTabs ref="playerTabs" :has-audio="hasAudio" :show-button="true"
|
|
||||||
@playerChanged="playerChanged" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-side">
|
|
||||||
<devicePtzPanel
|
|
||||||
:device-id="deviceId"
|
|
||||||
:channel-id="channelId"
|
|
||||||
@drag-zoom-start="toggleDragZoom"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" class="control-tabs">
|
|
||||||
<el-tab-pane label="预置位" name="preset">
|
|
||||||
<ptzPreset
|
|
||||||
v-if="tabActiveName === 'preset'"
|
|
||||||
:device-id="deviceId"
|
|
||||||
:channel-device-id="channelId"
|
|
||||||
style="margin-top: 8px;"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="实时视频" name="media">
|
|
||||||
<streamMediaPanel v-if="tabActiveName === 'media'" :player-url="playerUrlInfo.playerUrl" :play-url="playerUrlInfo.playUrl" :stream-info="streamInfo" />
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="编码信息" name="codec">
|
|
||||||
<mediaInfo v-if="tabActiveName === 'codec'" ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" />
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import elDragDialog from '@/directive/el-drag-dialog'
|
|
||||||
import playerTabs from '../../common/playerTabs.vue'
|
|
||||||
import devicePtzPanel from '../common/devicePtzPanel.vue'
|
|
||||||
import PtzPreset from './ptzPreset.vue'
|
|
||||||
import mediaInfo from '../../common/mediaInfo.vue'
|
|
||||||
import streamMediaPanel from '../../common/streamMediaPanel.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'DevicePlayer',
|
|
||||||
directives: { elDragDialog },
|
|
||||||
components: { playerTabs, devicePtzPanel, PtzPreset, mediaInfo, streamMediaPanel },
|
|
||||||
props: {},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
videoUrl: '',
|
|
||||||
streamId: '',
|
|
||||||
app: '',
|
|
||||||
mediaServerId: '',
|
|
||||||
deviceId: '',
|
|
||||||
channelId: '',
|
|
||||||
tabActiveName: 'preset',
|
|
||||||
hasAudio: false,
|
|
||||||
isLoging: false,
|
|
||||||
showVideoDialog: false,
|
|
||||||
streamInfo: null,
|
|
||||||
playerHeight: '48vh',
|
|
||||||
playerUrlInfo: {
|
|
||||||
playerUrl: null,
|
|
||||||
playUrl: null,
|
|
||||||
},
|
|
||||||
dragZoomDirection: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tabHandleClick: function(tab) {
|
|
||||||
if (tab.name === 'codec') {
|
|
||||||
this.$refs.mediaInfo && this.$refs.mediaInfo.startTask()
|
|
||||||
} else {
|
|
||||||
this.$refs.mediaInfo && this.$refs.mediaInfo.stopTask()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openDialog: function(tab, deviceId, channelId, param) {
|
|
||||||
if (this.showVideoDialog) return
|
|
||||||
this.tabActiveName = tab === 'streamPlay' ? 'media' : (tab || 'preset')
|
|
||||||
this.deviceId = deviceId
|
|
||||||
this.channelId = channelId
|
|
||||||
this.streamId = ''
|
|
||||||
this.mediaServerId = ''
|
|
||||||
this.app = ''
|
|
||||||
this.videoUrl = ''
|
|
||||||
if (param && param.streamInfo) {
|
|
||||||
this.play(param.streamInfo, param.hasAudio)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
play: function(streamInfo, hasAudio) {
|
|
||||||
this.streamInfo = streamInfo
|
|
||||||
this.hasAudio = hasAudio
|
|
||||||
this.isLoging = false
|
|
||||||
this.streamId = streamInfo.stream
|
|
||||||
this.app = streamInfo.app
|
|
||||||
this.mediaServerId = streamInfo.mediaServerId
|
|
||||||
this.showVideoDialog = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.setStreamInfo(streamInfo.transcodeStream || streamInfo)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
playerChanged: function(playerUrlInfo) {
|
|
||||||
this.playerUrlInfo = playerUrlInfo
|
|
||||||
},
|
|
||||||
close: function() {
|
|
||||||
if (this.$refs.playerTabs) {
|
|
||||||
this.$refs.playerTabs.stop()
|
|
||||||
}
|
|
||||||
this.videoUrl = ''
|
|
||||||
this.showVideoDialog = false
|
|
||||||
},
|
|
||||||
toggleDragZoom(direction) {
|
|
||||||
this.dragZoomDirection = direction
|
|
||||||
this.$refs.playerTabs.startDragZoom((params) => {
|
|
||||||
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 = ''
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#devicePlayer .el-dialog__body { padding: 10px 20px; }
|
|
||||||
.dhsdk-player-body { display: flex; gap: 16px; }
|
|
||||||
.player-side { flex: 3; min-width: 0; }
|
|
||||||
.player-container { width: 100%; }
|
|
||||||
.control-side { flex: 2; min-width: 340px; display: flex; flex-direction: column; }
|
|
||||||
.control-tabs { flex: 1; display: flex; flex-direction: column; min-height: 220px}
|
|
||||||
.control-tabs .el-tabs__content { flex: 1; overflow: auto; }
|
|
||||||
.media-info-content { overflow: auto; }
|
|
||||||
.media-row { display: flex; margin-bottom: 0.5rem; height: 2.5rem; }
|
|
||||||
.media-label { width: 6rem; line-height: 2.5rem; text-align: right; flex-shrink: 0; }
|
|
||||||
.trank { width: 80%; height: 180px; text-align: left; padding: 0 10%; overflow: auto; }
|
|
||||||
</style>
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="ptzPreset" style="width: 100%">
|
|
||||||
<el-tag
|
|
||||||
v-for="item in presetList"
|
|
||||||
:key="item.presetId"
|
|
||||||
size="mini"
|
|
||||||
style="margin-right: 1rem; cursor: pointer; margin-bottom: 0.6rem"
|
|
||||||
@click="gotoPreset(item)"
|
|
||||||
>
|
|
||||||
{{ item.presetName || item.presetId }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PtzPreset',
|
|
||||||
props: ['channelDeviceId', 'deviceId'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
presetList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getPresetList()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPresetList: function() {
|
|
||||||
this.$store.dispatch('frontEnd/queryPreset', [this.deviceId, this.channelDeviceId])
|
|
||||||
.then(data => {
|
|
||||||
this.presetList = data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
gotoPreset: function(preset) {
|
|
||||||
this.$store.dispatch('frontEnd/callPreset', [this.deviceId, this.channelDeviceId, preset.presetId])
|
|
||||||
.then(() => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '调用成功',
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -195,9 +195,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import deviceEdit from './edit.vue'
|
import deviceEdit from './edit.vue'
|
||||||
import syncChannelProgress from './dialog/SyncChannelProgress.vue'
|
import syncChannelProgress from '../dialog/SyncChannelProgress.vue'
|
||||||
import configInfo from '../dialog/configInfo.vue'
|
import configInfo from '../dialog/configInfo.vue'
|
||||||
import timeStatistics from './dialog/timeStatistics.vue'
|
import timeStatistics from './timeStatistics.vue'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -75,12 +75,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="位置信息" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<span v-if="scope.row.gbLongitude && scope.row.gbLatitude">{{ scope.row.gbLongitude }}<br>{{ scope.row.gbLatitude }}</span>
|
|
||||||
<span v-if="!scope.row.gbLongitude || !scope.row.gbLatitude">无</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" min-width="100">
|
<el-table-column label="状态" min-width="100">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<div slot="reference" class="name-wrapper">
|
<div slot="reference" class="name-wrapper">
|
||||||
|
|||||||
@ -90,12 +90,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="位置信息" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<span v-if="scope.row.gbLongitude && scope.row.gbLatitude">{{ scope.row.gbLongitude }}<br>{{ scope.row.gbLatitude }}</span>
|
|
||||||
<span v-if="!scope.row.gbLongitude || !scope.row.gbLatitude">无</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" min-width="100">
|
<el-table-column label="状态" min-width="100">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<div slot="reference" class="name-wrapper">
|
<div slot="reference" class="name-wrapper">
|
||||||
@ -90,12 +90,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="位置信息" min-width="150">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<span v-if="scope.row.gbLongitude && scope.row.gbLatitude">{{ scope.row.gbLongitude }}<br>{{ scope.row.gbLatitude }}</span>
|
|
||||||
<span v-if="!scope.row.gbLongitude || !scope.row.gbLatitude">无</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" min-width="100">
|
<el-table-column label="状态" min-width="100">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<div slot="reference" class="name-wrapper">
|
<div slot="reference" class="name-wrapper">
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user