Merge branch 'master' into 重构/1078

# Conflicts:
#	web/src/router/index.js
#	web/src/views/common/channelPlayer/chooseChannelForJt.vue
#	web/src/views/common/channelPlayer/jtDeviceEdit.vue
#	web/src/views/common/channelPlayer/jtDevicePlayer.vue
#	web_src/src/layout/UiHeader.vue
#	web_src/src/main.js
#	web_src/src/router/index.js
This commit is contained in:
lin 2025-07-03 09:41:55 +08:00
commit 465e03d15d
262 changed files with 3446 additions and 40754 deletions

View File

@ -27,12 +27,6 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn)
ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)
# 付费社群
[![社群](doc/_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接自行推出,星球会直接退款给大家。
> 星球还提供了包括闭源的全功能试用包, 会随时更新。
# gitee仓库
https://gitee.com/pan648540858/wvp-GB28181-pro.git
@ -133,6 +127,12 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
- [X] 支持部标1078+808协议支持点播云台控制录像回放位置上报自动点播等。
- [X] 支持国标28181-2022协议支持巡航轨迹查询PTZ精准控制存储卡格式化设备软件升级OSD配置h265+aac支持辅码流录像倒放等。
- [X] 支持国网B接口协议。支持注册获取资源预览, 云台控制,预置位控制等,可免费定制支持语音对讲、录像回放和抓拍图像。
- [X] 功能加强版本
- [X] 支持按权限分配可以使用的通道
- [X] 支持电子地图。支持展示通道位置支持在地图上修改通道位置。可扩展接入高德地图API支持搜索位置附近设备。
- [X] 支持表格导出
- [X] 拉流代理支持按照品牌拼接url。
- [X] 功能持续扩展,可根据用户需要增加支持。
# 授权协议
@ -140,6 +140,17 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
# 技术支持
# 付费社群
<img src="doc/_media/shequ.png" width="50%" height="50%">
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。
> 加入三天内不满意可以直接自行推出,星球会直接退款给大家。需要发票可以在星球app中直接咨询星球客服获取。
> 星球还提供了包括闭源的全功能试用包, 会随时更新。
> 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题。如果暂时无法加入,给项目点个星也是极大的鼓励。
[知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:,
- [使用入门系列一WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp)

View File

@ -39,9 +39,9 @@ public class SpringDocConfig {
.info(new Info().title("WVP-PRO 接口文档")
.contact(contact)
.description("开箱即用的28181协议视频平台。 <br/>" +
"1. 打开http://127.0.0.1:18080/doc.html#/1.%20全部/用户管理/login_1" +
"1. 打开<a href='/doc.html#/1.%20全部/用户管理/login_1'>登录</a>接口" +
" 登录成功后返回AccessToken。 <br/>" +
"2. 填写到AccessToken到参数值 http://127.0.0.1:18080/doc.html#/Authorize/1.%20全部 <br/>" +
"2. 填写到AccessToken到参数值 <a href='/doc.html#/Authorize/1.%20全部'>Token配置</a> <br/>" +
"后续接口就可以直接测试了")
.version("v3.1.0")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));

View File

@ -204,6 +204,9 @@ public class UserSetting {
*/
private boolean sipCacheServerConnections = true;
/**
* 禁用date头变相禁用了校时
*/
private boolean disableDateHeader = false;
}

View File

@ -21,6 +21,12 @@ public class DeviceChannel extends CommonGBChannel {
@Schema(description = "数据库自增ID")
private int id;
@Schema(description = "父设备编码")
private String parentDeviceId;
@Schema(description = "父设备名称")
private String parentName;
@MessageElementForCatalog("DeviceID")
@Schema(description = "编码")
private String deviceId;

View File

@ -0,0 +1,43 @@
package com.genersoft.iot.vmp.gb28181.bean;
import lombok.Getter;
import lombok.Setter;
import org.dom4j.Element;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class MessageResponseTask<T> implements Delayed {
@Getter
@Setter
private Element element;
@Getter
@Setter
private List<T> data;
@Getter
@Setter
private String key;
/**
* 超时时间(单位 毫秒)
*/
@Getter
@Setter
private long delayTime;
@Override
public long getDelay(@NotNull TimeUnit unit) {
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NotNull Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
}

View File

@ -148,6 +148,7 @@ public class AlarmController {
@Parameter(name = "page",description = "当前页",required = true)
@Parameter(name = "count",description = "每页查询数量",required = true)
@Parameter(name = "deviceId",description = "设备id")
@Parameter(name = "channelId",description = "通道id")
@Parameter(name = "alarmPriority",description = "查询内容")
@Parameter(name = "alarmMethod",description = "查询内容")
@Parameter(name = "alarmType",description = "每页查询数量")
@ -157,7 +158,8 @@ public class AlarmController {
public PageInfo<DeviceAlarm> getAll(
@RequestParam int page,
@RequestParam int count,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false) String channelId,
@RequestParam(required = false) String alarmPriority,
@RequestParam(required = false) String alarmMethod,
@RequestParam(required = false) String alarmType,
@ -186,7 +188,7 @@ public class AlarmController {
throw new ControllerException(ErrorCode.ERROR400.getCode(), "endTime格式为" + DateUtil.PATTERN);
}
return deviceAlarmService.getAllAlarm(page, count, deviceId, alarmPriority, alarmMethod,
return deviceAlarmService.getAllAlarm(page, count, deviceId, channelId, alarmPriority, alarmMethod,
alarmType, startTime, endTime);
}
}

View File

@ -113,6 +113,19 @@ public class DeviceQuery {
return deviceChannelService.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
}
@GetMapping("/streams")
@Operation(summary = "分页查询存在流的通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "page", description = "当前页", required = true)
@Parameter(name = "count", description = "每页查询数量", required = true)
@Parameter(name = "query", description = "查询内容")
public PageInfo<DeviceChannel> streamChannels(int page, int count,
@RequestParam(required = false) String query) {
if (ObjectUtils.isEmpty(query)) {
query = null;
}
return deviceChannelService.queryChannels(query, true, null, null, true, page, count);
}
@Operation(summary = "同步设备通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)

View File

@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Group;
import com.genersoft.iot.vmp.gb28181.bean.GroupTree;
import com.genersoft.iot.vmp.gb28181.service.IGroupService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -33,7 +34,7 @@ public class GroupController {
groupService.add(group);
}
@Operation(summary = "查询分组")
@Operation(summary = "查询分组节点")
@Parameter(name = "query", description = "要搜索的内容", required = true)
@Parameter(name = "parent", description = "所属分组编号", required = true)
@ResponseBody
@ -49,6 +50,17 @@ public class GroupController {
return groupService.queryForTree(query, parent, hasChannel);
}
@Operation(summary = "查询分组")
@Parameter(name = "query", description = "要搜索的内容", required = true)
@Parameter(name = "channel", description = "true为查询通道false为查询节点", required = true)
@ResponseBody
@GetMapping("/tree/query")
public PageInfo<Group> queryTree(Integer page, Integer count,
@RequestParam(required = true) String query
){
return groupService.queryList(page, count, query);
}
@Operation(summary = "更新分组")
@Parameter(name = "group", description = "Group", required = true)
@ResponseBody

View File

@ -7,19 +7,26 @@ import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
@Tag(name = "媒体流相关")
@ -52,11 +59,12 @@ public class MediaController {
@Parameter(name = "useSourceIpAsStreamIp", description = "是否使用请求IP作为返回的地址IP")
@GetMapping(value = "/stream_info_by_app_and_stream")
@ResponseBody
public StreamContent getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app,
@RequestParam String stream,
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId,
@RequestParam(required = false) Boolean useSourceIpAsStreamIp){
public DeferredResult<WVPResult<StreamContent>> getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app,
@RequestParam String stream,
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId,
@RequestParam(required = false) Boolean useSourceIpAsStreamIp){
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>();
boolean authority = false;
if (callId != null) {
// 权限校验
@ -75,9 +83,7 @@ public class MediaController {
authority = true;
}
}
StreamInfo streamInfo;
if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) {
String host = request.getHeader("Host");
String localAddr = host.split(":")[0];
@ -88,30 +94,37 @@ public class MediaController {
}
if (streamInfo != null){
return new StreamContent(streamInfo);
WVPResult<StreamContent> wvpResult = WVPResult.success();
wvpResult.setData(new StreamContent(streamInfo));
result.setResult(wvpResult);
}else {
ErrorCallback<StreamInfo> callback = (code, msg, streamInfoStoStart) -> {
if (code == InviteErrorCode.SUCCESS.getCode()) {
WVPResult<StreamContent> wvpResult = WVPResult.success();
if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) {
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfoStoStart.changeStreamIp(host);
}
if (!ObjectUtils.isEmpty(streamInfoStoStart.getMediaServer().getTranscodeSuffix())
&& !"null".equalsIgnoreCase(streamInfoStoStart.getMediaServer().getTranscodeSuffix())) {
streamInfoStoStart.setStream(streamInfoStoStart.getStream() + "_" + streamInfoStoStart.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfoStoStart));
result.setResult(wvpResult);
}else {
result.setResult(WVPResult.fail(code, msg));
}
};
//获取流失败重启拉流后重试一次
streamProxyService.stopByAppAndStream(app,stream);
boolean start = streamProxyService.startByAppAndStream(app, stream);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error("[线程休眠失败] {}", e.getMessage());
}
if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) {
String host = request.getHeader("Host");
String localAddr = host.split(":")[0];
log.info("使用{}作为返回流的ip", localAddr);
streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, localAddr, authority);
}else {
streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority);
}
if (streamInfo != null){
return new StreamContent(streamInfo);
}else {
throw new ControllerException(ErrorCode.ERROR100);
}
streamProxyService.startByAppAndStream(app, stream, callback);
}
return result;
}
/**
* 获取推流播放地址

View File

@ -50,20 +50,29 @@ public class RegionController {
return regionService.query(query, page, count);
}
@Operation(summary = "查询区域")
@Operation(summary = "查询区域节点")
@Parameter(name = "query", description = "要搜索的内容", required = true)
@Parameter(name = "parent", description = "所属行政区划编号", required = true)
@Parameter(name = "hasChannel", description = "是否查询通道", required = true)
@ResponseBody
@GetMapping("/tree/list")
public List<RegionTree> queryForTree(
@RequestParam(required = false) String query,
@RequestParam(required = false) Integer parent,
@RequestParam(required = false) Boolean hasChannel
){
if (ObjectUtils.isEmpty(query)) {
query = null;
}
return regionService.queryForTree(query, parent, hasChannel);
return regionService.queryForTree(parent, hasChannel);
}
@Operation(summary = "查询区域")
@Parameter(name = "query", description = "要搜索的内容", required = true)
@Parameter(name = "channel", description = "true为查询通道false为查询节点", required = true)
@ResponseBody
@GetMapping("/tree/query")
public PageInfo<Region> queryTree(Integer page, Integer count,
@RequestParam(required = true) String query
){
return regionService.queryList(page, count, query);
}
@Operation(summary = "更新区域")

View File

@ -275,10 +275,8 @@ public interface CommonGBChannelMapper {
" true as is_leaf " +
" from wvp_device_channel " +
" where coalesce(gb_civil_code, civil_code) = #{parentDeviceId} " +
" <if test='query != null'> AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') " +
" OR coalesce(gb_name, name) LIKE concat('%',#{query},'%'))</if> " +
" </script>")
List<RegionTree> queryForRegionTreeByCivilCode(@Param("query") String query, @Param("parentDeviceId") String parentDeviceId);
List<RegionTree> queryForRegionTreeByCivilCode(@Param("parentDeviceId") String parentDeviceId);
@Update(value = {" <script>" +
" UPDATE wvp_device_channel " +

View File

@ -26,6 +26,7 @@ public interface DeviceAlarmMapper {
" SELECT * FROM wvp_device_alarm " +
" WHERE 1=1 " +
" <if test=\"deviceId != null\" > AND device_id = #{deviceId}</if>" +
" <if test=\"channelId != null\" > AND channel_id = #{channelId}</if>" +
" <if test=\"alarmPriority != null\" > AND alarm_priority = #{alarmPriority} </if>" +
" <if test=\"alarmMethod != null\" > AND alarm_method = #{alarmMethod} </if>" +
" <if test=\"alarmType != null\" > AND alarm_type = #{alarmType} </if>" +
@ -33,7 +34,7 @@ public interface DeviceAlarmMapper {
" <if test=\"endTime != null\" > AND alarm_time &lt;= #{endTime} </if>" +
" ORDER BY alarm_time ASC " +
" </script>"})
List<DeviceAlarm> query(@Param("deviceId") String deviceId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod,
List<DeviceAlarm> query(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod,
@Param("alarmType") String alarmType, @Param("startTime") String startTime, @Param("endTime") String endTime);

View File

@ -86,10 +86,11 @@ public interface DeviceChannelMapper {
int update(DeviceChannel channel);
@SelectProvider(type = DeviceChannelProvider.class, method = "queryChannels")
List<DeviceChannel> queryChannels(@Param("dataDeviceId") int dataDeviceId, @Param("civilCode") String civilCode,
List<DeviceChannel> queryChannels(@Param("dataDeviceId") Integer dataDeviceId, @Param("civilCode") String civilCode,
@Param("businessGroupId") String businessGroupId, @Param("parentChannelId") String parentChannelId,
@Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel,
@Param("online") Boolean online, @Param("channelIds") List<String> channelIds);
@Param("query") String query, @Param("queryParent") Boolean queryParent,
@Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online,
@Param("channelIds") List<String> channelIds, @Param("hasStream") Boolean hasStream);
@SelectProvider(type = DeviceChannelProvider.class, method = "queryChannelsByDeviceDbId")
List<DeviceChannel> queryChannelsByDeviceDbId(@Param("dataDeviceId") int dataDeviceId);

View File

@ -346,38 +346,41 @@ public interface DeviceMapper {
@Select(" <script>" +
"SELECT " +
"id, " +
"device_id, " +
"coalesce(custom_name, name) as name, " +
"password, " +
"manufacturer, " +
"model, " +
"firmware, " +
"transport," +
"stream_mode," +
"ip,"+
"sdp_ip,"+
"local_ip,"+
"port,"+
"host_address,"+
"expires,"+
"register_time,"+
"keepalive_time,"+
"create_time,"+
"update_time,"+
"charset,"+
"subscribe_cycle_for_catalog,"+
"subscribe_cycle_for_mobile_position,"+
"mobile_position_submission_interval,"+
"subscribe_cycle_for_alarm,"+
"ssrc_check,"+
"as_message_channel,"+
"broadcast_push_after_ack,"+
"geo_coord_sys,"+
"on_line,"+
"media_server_id,"+
"server_id,"+
"(SELECT count(0) FROM wvp_device_channel dc WHERE dc.data_type = #{dataType} and dc.data_device_id= de.id) as channel_count " +
"id" +
",device_id" +
",manufacturer" +
",model" +
",firmware" +
",transport" +
",stream_mode" +
",on_line" +
",register_time" +
",keepalive_time" +
",ip" +
",create_time" +
",update_time" +
",port" +
",expires" +
",subscribe_cycle_for_catalog" +
",subscribe_cycle_for_mobile_position" +
",mobile_position_submission_interval" +
",subscribe_cycle_for_alarm" +
",host_address" +
",charset" +
",ssrc_check" +
",geo_coord_sys" +
",media_server_id" +
",sdp_ip" +
",local_ip" +
",password" +
",as_message_channel" +
",heart_beat_interval" +
",heart_beat_count" +
",position_capability" +
",broadcast_push_after_ack" +
",server_id" +
",(SELECT count(0) FROM wvp_device_channel dc WHERE dc.data_type = #{dataType} and dc.data_device_id= de.id) as channel_count " +
" FROM wvp_device de" +
" where 1 = 1 "+
" <if test='status != null'> AND de.on_line=${status}</if>"+
@ -423,4 +426,36 @@ public interface DeviceMapper {
"<foreach collection='offlineDevices' item='item' open='(' separator=',' close=')' > #{item.id}</foreach>" +
" </script>"})
void offlineByList(List<Device> offlineDevices);
@Update({"<script>" +
"<foreach collection='devices' item='item' separator=';'>" +
" UPDATE" +
" wvp_device" +
" SET update_time=#{item.updateTime}" +
", name=#{item.name}" +
", manufacturer=#{item.manufacturer}" +
", model=#{item.model}" +
", firmware=#{item.firmware}" +
", transport=#{item.transport}" +
", ip=#{item.ip}" +
", local_ip=#{item.localIp}" +
", port=#{item.port}" +
", host_address=#{item.hostAddress}" +
", on_line=#{item.onLine}" +
", register_time=#{item.registerTime}" +
", keepalive_time=#{item.keepaliveTime}" +
", heart_beat_interval=#{item.heartBeatInterval}" +
", position_capability=#{item.positionCapability}" +
", heart_beat_count=#{item.heartBeatCount}" +
", subscribe_cycle_for_catalog=#{item.subscribeCycleForCatalog}" +
", subscribe_cycle_for_mobile_position=#{item.subscribeCycleForMobilePosition}" +
", mobile_position_submission_interval=#{item.mobilePositionSubmissionInterval}" +
", subscribe_cycle_for_alarm=#{item.subscribeCycleForAlarm}" +
", expires=#{item.expires}" +
", server_id=#{item.serverId}" +
" WHERE device_id=#{item.deviceId}"+
"</foreach>" +
"</script>"})
void batchUpdate(List<Device> devices);
}

View File

@ -80,9 +80,8 @@ public interface RegionMapper {
" where " +
" <if test='parentId != null'> parent_id = #{parentId} </if> " +
" <if test='parentId == null'> parent_id is null </if> " +
" <if test='query != null'> AND (device_id LIKE concat('%',#{query},'%') escape '/' OR name LIKE concat('%',#{query},'%') escape '/')</if> " +
" </script>")
List<RegionTree> queryForTree(@Param("query") String query, @Param("parentId") Integer parentId);
List<RegionTree> queryForTree(@Param("parentId") Integer parentId);
@Delete("<script>" +
" DELETE FROM wvp_common_region WHERE id in " +

View File

@ -20,6 +20,8 @@ public class DeviceChannelProvider {
" dc.gps_time,\n" +
" dc.stream_identification,\n" +
" dc.channel_type,\n" +
" d.device_id as parent_device_id,\n" +
" coalesce(d.custom_name, d.name) as parent_name,\n" +
" coalesce(dc.gb_device_id, dc.device_id) as device_id,\n" +
" coalesce(dc.gb_name, dc.name) as name,\n" +
" coalesce(dc.gb_manufacturer, dc.manufacturer) as manufacturer,\n" +
@ -55,13 +57,17 @@ public class DeviceChannelProvider {
" coalesce(dc.gb_svc_space_support_mod, dc.svc_space_support_mod) as svc_space_support_mod,\n" +
" coalesce(dc.gb_svc_time_support_mode,dc.svc_time_support_mode) as svc_time_support_mode\n" +
" from " +
" wvp_device_channel dc "
" wvp_device_channel dc " +
" left join wvp_device d on d.id = dc.data_device_id "
;
}
public String queryChannels(Map<String, Object> params ){
StringBuilder sqlBuild = new StringBuilder();
sqlBuild.append(getBaseSelectSql());
sqlBuild.append(" where data_type = " + ChannelDataType.GB28181.value + " and dc.data_device_id = #{dataDeviceId} ");
sqlBuild.append(" where data_type = " + ChannelDataType.GB28181.value);
if (params.get("dataDeviceId") != null) {
sqlBuild.append(" AND dc.data_device_id = #{dataDeviceId} ");
}
if (params.get("businessGroupId") != null ) {
sqlBuild.append(" AND coalesce(dc.gb_business_group_id, dc.business_group_id)=#{businessGroupId} AND coalesce(dc.gb_parent_id, dc.parent_id) is null");
}else if (params.get("parentChannelId") != null ) {
@ -73,8 +79,15 @@ public class DeviceChannelProvider {
}
if (params.get("query") != null && !ObjectUtils.isEmpty(params.get("query"))) {
sqlBuild.append(" AND (coalesce(dc.gb_device_id, dc.device_id) LIKE concat('%',#{query},'%') escape '/'" +
" OR coalesce(dc.gb_name, dc.name) LIKE concat('%',#{query},'%') escape '/')")
;
" OR coalesce(dc.gb_name, dc.name) LIKE concat('%',#{query},'%') escape '/'");
if (params.get("queryParent") != null && (Boolean) params.get("queryParent")) {
sqlBuild.append(" OR d.device_id LIKE concat('%',#{query},'%') escape '/'");
sqlBuild.append(" OR coalesce(d.custom_name, d.name) LIKE concat('%',#{query},'%') escape '/'");
}
sqlBuild.append(")");
}
if (params.get("hasStream") != null && (Boolean) params.get("hasStream")) {
sqlBuild.append(" AND dc.stream_id IS NOT NULL");
}
if (params.get("online") != null && (Boolean)params.get("online")) {
sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'");
@ -101,7 +114,7 @@ public class DeviceChannelProvider {
}
sqlBuild.append(" )");
}
sqlBuild.append(" ORDER BY device_id");
sqlBuild.append(" ORDER BY d.device_id, dc.device_id");
return sqlBuild.toString();
}

View File

@ -23,7 +23,7 @@ public interface IDeviceAlarmService {
* @param endTime 结束时间
* @return 报警列表
*/
PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String alarmPriority, String alarmMethod,
PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod,
String alarmType, String startTime, String endTime);
/**

View File

@ -1,10 +1,7 @@
package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.common.enums.DeviceControlType;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelReduce;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
@ -104,6 +101,7 @@ public interface IDeviceChannelService {
PageInfo<DeviceChannel> queryChannelsByDeviceId(String deviceId, String query, Boolean channelType, Boolean online, int page, int count);
PageInfo<DeviceChannel> queryChannels(String query, Boolean queryParent, Boolean channelType, Boolean online, Boolean hasStream, int page, int count);
List<Device> queryDeviceWithAsMessageChannel();

View File

@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@ -117,6 +118,9 @@ public interface IDeviceService {
*/
void updateDevice(Device device);
@Transactional
void updateDeviceList(List<Device> deviceList);
/**
* 检查设备编号是否已经存在
* @param deviceId 设备编号

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.gb28181.bean.Group;
import com.genersoft.iot.vmp.gb28181.bean.GroupTree;
import com.github.pagehelper.PageInfo;
import java.util.List;
@ -23,4 +24,6 @@ public interface IGroupService {
boolean batchAdd(List<Group> groupList);
List<Group> getPath(String deviceId, String businessGroup);
PageInfo<Group> queryList(Integer page, Integer count, String query);
}

View File

@ -27,7 +27,7 @@ public interface IRegionService {
Region queryRegionByDeviceId(String regionDeviceId);
List<RegionTree> queryForTree(String query, Integer parent, Boolean hasChannel);
List<RegionTree> queryForTree(Integer parent, Boolean hasChannel);
void syncFromChannel();
@ -40,4 +40,6 @@ public interface IRegionService {
String getDescription(String civilCode);
void addByCivilCode(String civilCode);
PageInfo<Region> queryList(int page, int count, String query);
}

View File

@ -17,9 +17,9 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
private DeviceAlarmMapper deviceAlarmMapper;
@Override
public PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String alarmPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
public PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
PageHelper.startPage(page, count);
List<DeviceAlarm> all = deviceAlarmMapper.query(deviceId, alarmPriority, alarmMethod, alarmType, startTime, endTime);
List<DeviceAlarm> all = deviceAlarmMapper.query(deviceId, channelId, alarmPriority, alarmMethod, alarmType, startTime, endTime);
return new PageInfo<>(all);
}

View File

@ -696,7 +696,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
.replaceAll("%", "/%")
.replaceAll("_", "/_");
}
List<DeviceChannel> all = channelMapper.queryChannels(deviceDbId, civilCode, businessGroupId, parentId, query, channelType, online,null);
List<DeviceChannel> all = channelMapper.queryChannels(deviceDbId, civilCode, businessGroupId, parentId, query, false, channelType, online, null, null);
return new PageInfo<>(all);
}
@ -717,7 +717,19 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
.replaceAll("_", "/_");
}
PageHelper.startPage(page, count);
List<DeviceChannel> all = channelMapper.queryChannels(device.getId(), null,null, null, query, hasSubChannel, online,null);
List<DeviceChannel> all = channelMapper.queryChannels(device.getId(), null, null, null, query, false, hasSubChannel, online, null, null);
return new PageInfo<>(all);
}
@Override
public PageInfo<DeviceChannel> queryChannels(String query, Boolean queryParent, Boolean hasSubChannel, Boolean online, Boolean hasStream, int page, int count) {
PageHelper.startPage(page, count);
if (query != null) {
query = query.replaceAll("/", "//")
.replaceAll("%", "/%")
.replaceAll("_", "/_");
}
List<DeviceChannel> all = channelMapper.queryChannels(null, null, null, null, query, queryParent, hasSubChannel, online, null, hasStream);
return new PageInfo<>(all);
}

View File

@ -506,7 +506,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
if (device == null || device.getSubscribeCycleForCatalog() < 0) {
return false;
}
log.info("[添加目录订阅] 设备 {}", device.getDeviceId());
if (transactionInfo == null) {
log.info("[添加目录订阅] 设备 {}", device.getDeviceId());
}else {
log.info("[目录订阅续期] 设备 {}", device.getDeviceId());
}
try {
sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> {
ResponseEvent event = (ResponseEvent) eventResult.event;
@ -514,8 +518,8 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
log.info("[目录订阅]成功: {}", device.getDeviceId());
if (!subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
SIPResponse response = (SIPResponse) event.getResponse();
SipTransactionInfo transactionInfoForResonse = new SipTransactionInfo(response);
SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResonse);
SipTransactionInfo transactionInfoForResponse = new SipTransactionInfo(response);
SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResponse);
if (subscribeTask != null) {
subscribeTaskRunner.addSubscribe(subscribeTask);
}
@ -566,7 +570,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
@Override
public boolean addMobilePositionSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) {
log.info("[添加移动位置订阅] 设备 {}", device.getDeviceId());
if (transactionInfo == null) {
log.info("[添加移动位置订阅] 设备 {}", device.getDeviceId());
}else {
log.info("[移动位置订阅续期] 设备 {}", device.getDeviceId());
}
try {
sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> {
ResponseEvent event = (ResponseEvent) eventResult.event;
@ -574,13 +582,13 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
log.info("[移动位置订阅]成功: {}", device.getDeviceId());
if (!subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
SIPResponse response = (SIPResponse) event.getResponse();
SipTransactionInfo transactionInfoForResonse = new SipTransactionInfo(response);
SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResonse);
SipTransactionInfo transactionInfoForResponse = new SipTransactionInfo(response);
SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::mobilPositionSubscribeExpire, transactionInfoForResponse);
if (subscribeTask != null) {
subscribeTaskRunner.addSubscribe(subscribeTask);
}
}else {
subscribeTaskRunner.updateDelay(SubscribeTaskForMobilPosition.getKey(device), (device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis());
subscribeTaskRunner.updateDelay(SubscribeTaskForMobilPosition.getKey(device), (device.getSubscribeCycleForMobilePosition() * 1000L - 500L) + System.currentTimeMillis());
}
},eventResult -> {
@ -721,8 +729,6 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
@Override
public void updateDevice(Device device) {
String now = DateUtil.getNow();
device.setUpdateTime(now);
device.setCharset(device.getCharset() == null ? "" : device.getCharset().toUpperCase());
device.setUpdateTime(DateUtil.getNow());
if (deviceMapper.update(device) > 0) {
@ -730,6 +736,40 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
}
}
@Transactional
@Override
public void updateDeviceList(List<Device> deviceList) {
if (deviceList.isEmpty()){
log.info("[批量更新设备] 列表为空,更细失败");
return;
}
if (deviceList.size() == 1) {
updateDevice(deviceList.get(0));
}else {
for (Device device : deviceList) {
device.setCharset(device.getCharset() == null ? "" : device.getCharset().toUpperCase());
device.setUpdateTime(DateUtil.getNow());
}
int limitCount = 300;
if (!deviceList.isEmpty()) {
if (deviceList.size() > limitCount) {
for (int i = 0; i < deviceList.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > deviceList.size()) {
toIndex = deviceList.size();
}
deviceMapper.batchUpdate(deviceList.subList(i, toIndex));
}
}else {
deviceMapper.batchUpdate(deviceList);
}
for (Device device : deviceList) {
redisCatchStorage.updateDevice(device);
}
}
}
}
@Override
public boolean isExist(String deviceId) {
return getDeviceByDeviceIdFromDb(deviceId) != null;
@ -866,7 +906,16 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
public void subscribeMobilePosition(int id, int cycle, int interval) {
Device device = deviceMapper.query(id);
Assert.notNull(device, "未找到设备");
Assert.isTrue(device.isOnLine(), "设备已离线");
if (!device.isOnLine()) {
// 开启订阅
device.setSubscribeCycleForMobilePosition(cycle);
device.setMobilePositionSubmissionInterval(interval);
updateDevice(device);
if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device));
}
throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备已离线");
}
if (device.getSubscribeCycleForMobilePosition() == cycle) {
return;
@ -882,6 +931,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
// 开启订阅
device.setSubscribeCycleForMobilePosition(cycle);
device.setMobilePositionSubmissionInterval(interval);
updateDevice(device);
if (cycle > 0) {
addMobilePositionSubscribe(device, null);
}

View File

@ -118,6 +118,7 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
@Override
public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback<StreamInfo> callback) {
log.info("[通用通道] 播放, 类型: {} 编号:{}", channel.getDataType(), channel.getGbDeviceId());
if (channel.getDataType() == ChannelDataType.GB28181.value) {
// 国标通道
playGbDeviceChannel(channel, record, callback);

View File

@ -10,6 +10,8 @@ import com.genersoft.iot.vmp.gb28181.service.IGbChannelService;
import com.genersoft.iot.vmp.gb28181.service.IGroupService;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -293,4 +295,16 @@ public class GroupServiceImpl implements IGroupService {
allParent.add(parent);
return allParent;
}
@Override
public PageInfo<Group> queryList(Integer page, Integer count, String query) {
PageHelper.startPage(page, count);
if (query != null) {
query = query.replaceAll("/", "//")
.replaceAll("%", "/%")
.replaceAll("_", "/_");
}
List<Group> all = groupManager.query(query, null, null);
return new PageInfo<>(all);
}
}

View File

@ -144,17 +144,12 @@ public class RegionServiceImpl implements IRegionService {
}
@Override
public List<RegionTree> queryForTree(String query, Integer parent, Boolean hasChannel) {
if (query != null) {
query = query.replaceAll("/", "//")
.replaceAll("%", "/%")
.replaceAll("_", "/_");
}
List<RegionTree> regionList = regionMapper.queryForTree(query, parent);
public List<RegionTree> queryForTree(Integer parent, Boolean hasChannel) {
List<RegionTree> regionList = regionMapper.queryForTree(parent);
if (parent != null && hasChannel != null && hasChannel) {
Region parentRegion = regionMapper.queryOne(parent);
if (parentRegion != null) {
List<RegionTree> channelList = commonGBChannelMapper.queryForRegionTreeByCivilCode(query, parentRegion.getDeviceId());
List<RegionTree> channelList = commonGBChannelMapper.queryForRegionTreeByCivilCode(parentRegion.getDeviceId());
regionList.addAll(channelList);
}
}
@ -324,4 +319,16 @@ public class RegionServiceImpl implements IRegionService {
parentId = region.getId();
}
}
@Override
public PageInfo<Region> queryList(int page, int count, String query) {
PageHelper.startPage(page, count);
if (query != null) {
query = query.replaceAll("/", "//")
.replaceAll("%", "/%")
.replaceAll("_", "/_");
}
List<Region> all = regionMapper.query(query, null);
return new PageInfo<>(all);
}
}

View File

@ -94,14 +94,7 @@ public class DeviceStatusTaskRunner {
return false;
}
log.debug("[更新状态任务时间] 编号: {}", key);
if (delayQueue.contains(task)) {
boolean remove = delayQueue.remove(task);
if (!remove) {
log.info("[更新状态任务时间] 从延时队列内移除失败: {}", key);
}
}
task.setDelayTime(expirationTime);
delayQueue.offer(task);
String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000);
redisTemplate.expire(redisKey, duration);

View File

@ -94,14 +94,7 @@ public class SubscribeTaskRunner{
return false;
}
log.info("[更新订阅任务时间] {}, 编号: {}", task.getName(), key);
if (delayQueue.contains(task)) {
boolean remove = delayQueue.remove(task);
if (!remove) {
log.info("[更新订阅任务时间] 从延时队列内移除失败: {}", key);
}
}
task.setDelayTime(expirationTime);
delayQueue.offer(task);
String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey());
Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000);
redisTemplate.expire(redisKey, duration);

View File

@ -12,7 +12,7 @@ public class SubscribeTaskForMobilPosition extends SubscribeTask {
public static final String name = "mobilPosition";
public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) {
if (device.getSubscribeCycleForCatalog() <= 0) {
if (device.getSubscribeCycleForMobilePosition() <= 0) {
return null;
}
SubscribeTaskForMobilPosition subscribeTaskForMobilPosition = new SubscribeTaskForMobilPosition();

View File

@ -114,14 +114,7 @@ public class PlatformStatusTaskRunner {
return false;
}
log.info("[更新平台注册任务时间] 平台上级编号: {}", platformServerId);
if (registerDelayQueue.contains(task)) {
boolean remove = registerDelayQueue.remove(task);
if (!remove) {
log.info("[更新平台注册任务时间] 从延时队列内移除失败: {}", platformServerId);
}
}
task.setDelayTime(expirationTime);
registerDelayQueue.offer(task);
String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), platformServerId);
Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000);
redisTemplate.expire(redisKey, duration);
@ -165,14 +158,7 @@ public class PlatformStatusTaskRunner {
return false;
}
log.info("[更新平台心跳任务时间] 平台上级编号: {}", platformServerId);
if (keepaliveTaskDelayQueue.contains(task)) {
boolean remove = keepaliveTaskDelayQueue.remove(task);
if (!remove) {
log.info("[更新平台心跳任务时间] 从延时队列内移除失败: {}", platformServerId);
}
}
task.setDelayTime(expirationTime);
keepaliveTaskDelayQueue.offer(task);
return true;
}

View File

@ -1136,9 +1136,9 @@ public class SIPCommander implements ISIPCommander {
}
cmdXml.append("</Query>\r\n");
MessageEvent<Object> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback);
MessageEvent<Object> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 4000L, callback);
messageSubscribe.addSubscribe(messageEvent);
log.info("[预置位查询] 设备编号: {} 通道编号: {} SN {}", device.getDeviceId(), channelId, sn);
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> {
messageSubscribe.removeSubscribe(messageEvent.getKey());

View File

@ -144,12 +144,15 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
// 携带授权头并且密码正确
response = getMessageFactory().createResponse(Response.OK, request);
// 添加date头
SIPDateHeader dateHeader = new SIPDateHeader();
// 使用自己修改的
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
// 如果主动禁用了Date头则不添加
if (!userSetting.isDisableDateHeader()) {
// 添加date头
SIPDateHeader dateHeader = new SIPDateHeader();
// 使用自己修改的
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
}
if (request.getExpires() == null) {
response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
@ -218,12 +221,15 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
private Response getRegisterOkResponse(Request request) throws ParseException {
// 携带授权头并且密码正确
Response response = getMessageFactory().createResponse(Response.OK, request);
// 添加date头
SIPDateHeader dateHeader = new SIPDateHeader();
// 使用自己修改的
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
// 如果主动禁用了Date头则不添加
if (!userSetting.isDisableDateHeader()) {
// 添加date头
SIPDateHeader dateHeader = new SIPDateHeader();
// 使用自己修改的
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
}
// 添加Contact头
response.addHeader(request.getHeader(ContactHeader.NAME));

View File

@ -69,6 +69,12 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
responseAck(request, Response.BAD_REQUEST);
return;
}
ExpiresHeader expires = request.getExpires();
if (expires == null) {
log.error("处理SUBSCRIBE请求 未获取到ExpiresHeader{}", evt.getRequest());
responseAck(request, Response.BAD_REQUEST, "missing expires");
return;
}
String platformId = SipUtils.getUserIdFromFromHeader(request);
String cmd = XmlUtil.getText(rootElement, "CmdType");
log.info("[收到订阅请求] 类型: {}, 来自: {}", cmd, platformId);
@ -181,7 +187,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
.append("<Result>OK</Result>\r\n")
.append("</Response>\r\n");
try {
int expires = request.getExpires().getExpires();
Platform parentPlatform = platformService.queryPlatformByServerGBId(platformId);

View File

@ -2,9 +2,12 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.event.MessageSubscribe;
import com.genersoft.iot.vmp.gb28181.event.sip.MessageEvent;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Element;
@ -28,6 +31,9 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
@Autowired
private IPlatformService platformService;
@Autowired
private MessageSubscribe messageSubscribe;
public void addHandler(String cmdType, IMessageHandler messageHandler) {
messageHandlerMap.put(cmdType, messageHandler);
}
@ -54,6 +60,8 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
return;
}
messageHandler.handForDevice(evt, device, element);
}else {
handMessageEvent(element, null);
}
}
@ -65,4 +73,21 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
messageHandler.handForPlatform(evt, parentPlatform, element);
}
}
public void handMessageEvent(Element element, Object data) {
String cmd = getText(element, "CmdType");
String sn = getText(element, "SN");
MessageEvent<Object> subscribe = (MessageEvent<Object>)messageSubscribe.getSubscribe(cmd + sn);
if (subscribe != null && subscribe.getCallback() != null) {
String result = getText(element, "Result");
if (result == null || "OK".equalsIgnoreCase(result) || data != null) {
subscribe.getCallback().run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), data);
}else {
subscribe.getCallback().run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), result);
}
messageSubscribe.removeSubscribe(cmd + sn);
}
}
}

View File

@ -87,6 +87,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
if (handlerCatchDataList.isEmpty()) {
return;
}
List<Device> deviceListForUpdate = new ArrayList<>();
for (SipMsgInfo sipMsgInfo : handlerCatchDataList) {
if (sipMsgInfo == null) {
continue;
@ -113,7 +114,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
device.setKeepaliveTime(DateUtil.getNow());
if (device.isOnLine()) {
deviceService.updateDevice(device);
deviceListForUpdate.add(device);
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
if (statusTaskRunner.containsKey(device.getDeviceId())) {
statusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis());
@ -125,6 +126,9 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
}
}
}
if (!deviceListForUpdate.isEmpty()) {
deviceService.updateDeviceList(deviceListForUpdate);
}
}
@Override

View File

@ -1,11 +1,8 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.MessageSubscribe;
import com.genersoft.iot.vmp.gb28181.event.sip.MessageEvent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import org.dom4j.Element;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
@ -13,8 +10,6 @@ import org.springframework.stereotype.Component;
import javax.sip.RequestEvent;
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
/**
* 命令类型 请求动作的应答
* 命令类型 设备控制, 报警通知, 设备目录信息查询, 目录信息查询, 目录收到, 设备信息查询, 设备状态信息查询 ......
@ -27,8 +22,7 @@ public class ResponseMessageHandler extends MessageHandlerAbstract implements In
@Autowired
private MessageRequestProcessor messageRequestProcessor;
@Autowired
private MessageSubscribe messageSubscribe;
@Override
public void afterPropertiesSet() throws Exception {
@ -38,21 +32,5 @@ public class ResponseMessageHandler extends MessageHandlerAbstract implements In
@Override
public void handForDevice(RequestEvent evt, Device device, Element element) {
super.handForDevice(evt, device, element);
handMessageEvent(element, null);
}
public void handMessageEvent(Element element, Object data) {
String cmd = getText(element, "CmdType");
String sn = getText(element, "SN");
MessageEvent<Object> subscribe = (MessageEvent<Object>)messageSubscribe.getSubscribe(cmd + sn);
if (subscribe != null && subscribe.getCallback() != null) {
String result = getText(element, "Result");
if (result == null || "OK".equalsIgnoreCase(result) || data != null) {
subscribe.getCallback().run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), data);
}else {
subscribe.getCallback().run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), result);
}
messageSubscribe.removeSubscribe(cmd + sn);
}
}
}

View File

@ -1,9 +1,9 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.MessageResponseTask;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.Preset;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
@ -13,6 +13,7 @@ import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
@ -23,6 +24,12 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
/**
* 设备预置位查询应答
@ -36,8 +43,9 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
@Autowired
private ResponseMessageHandler responseMessageHandler;
@Autowired
private DeferredResultHolder deferredResultHolder;
private final Map<String, MessageResponseTask<Preset>> mesageMap = new ConcurrentHashMap<>();
private final DelayQueue<MessageResponseTask<Preset>> delayQueue = new DelayQueue<>();
@Override
@ -93,7 +101,14 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
presetQuerySipReqList.add(presetQuerySipReq);
}
}
responseMessageHandler.handMessageEvent(rootElement, presetQuerySipReqList);
// if (presetQuerySipReqList.size() == sumNum) {
// responseMessageHandler.handMessageEvent(rootElement, presetQuerySipReqList);
// }else {
// String sn = getText(element, "SN");
// addCatch(cmdType + "_" + sn, rootElement, presetQuerySipReqList);
// }
String sn = getText(element, "SN");
addCatch(cmdType + "_" + sn, sumNum, rootElement, presetQuerySipReqList);
try {
responseAck(request, Response.OK);
} catch (InvalidArgumentException | ParseException | SipException e) {
@ -104,6 +119,63 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
}
}
private void addCatch(String key, int sumNum, Element rootElement, List<Preset> presetQuerySipReqList) {
if (presetQuerySipReqList.size() == sumNum) {
responseMessageHandler.handMessageEvent(rootElement, presetQuerySipReqList);
if (mesageMap.containsKey(key)) {
MessageResponseTask<Preset> messageResponseTask = mesageMap.get(key);
mesageMap.remove(key);
boolean remove = delayQueue.remove(messageResponseTask);
if (!remove) {
log.info("[移除预置位查询任务] 从延时队列内移除失败: {}", key);
}
}
}else {
if (mesageMap.containsKey(key)) {
MessageResponseTask<Preset> messageResponseTask = mesageMap.get(key);
List<Preset> data = messageResponseTask.getData();
data.addAll(presetQuerySipReqList);
if (data.size() == sumNum) {
responseMessageHandler.handMessageEvent(rootElement, presetQuerySipReqList);
mesageMap.remove(key);
boolean remove = delayQueue.remove(messageResponseTask);
if (!remove) {
log.info("[移除预置位查询任务] 从延时队列内移除失败: {}", key);
}
return;
}
messageResponseTask.setDelayTime(System.currentTimeMillis() + 1000);
}else {
MessageResponseTask<Preset> messageResponseTask = new MessageResponseTask<>();
messageResponseTask.setElement(rootElement);
messageResponseTask.setData(presetQuerySipReqList);
messageResponseTask.setDelayTime(System.currentTimeMillis() + 1000);
messageResponseTask.setKey(key);
mesageMap.put(key, messageResponseTask);
delayQueue.offer(messageResponseTask);
}
}
}
// 处理过期的缓存
@Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS)
public void expirationCheck(){
while (!delayQueue.isEmpty()) {
MessageResponseTask<Preset> take = null;
try {
take = delayQueue.take();
try {
responseMessageHandler.handMessageEvent(take.getElement(), take.getData());
mesageMap.remove(take.getKey());
}catch (Exception e) {
log.error("[预置位查询到期] {} 到期处理时出现异常", take.getKey());
}
} catch (InterruptedException e) {
log.error("[设备订阅任务] ", e);
}
}
}
@Override
public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) {

View File

@ -64,7 +64,7 @@ public interface IMediaNodeServerService {
Long updateDownloadProcess(MediaServer mediaServer, String app, String stream);
StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy);
void startProxy(MediaServer mediaServer, StreamProxy streamProxy);
void stopProxy(MediaServer mediaServer, String streamKey);

View File

@ -150,7 +150,7 @@ public interface IMediaServerService {
Long updateDownloadProcess(MediaServer mediaServerItem, String app, String stream);
StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy);
void startProxy(MediaServer mediaServer, StreamProxy streamProxy);
void stopProxy(MediaServer mediaServer, String streamKey);

View File

@ -952,13 +952,13 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
@Override
public StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy) {
public void startProxy(MediaServer mediaServer, StreamProxy streamProxy) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[startProxy] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
return mediaNodeServerService.startProxy(mediaServer, streamProxy);
mediaNodeServerService.startProxy(mediaServer, streamProxy);
}
@Override

View File

@ -425,7 +425,7 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
@Override
public StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy) {
public void startProxy(MediaServer mediaServer, StreamProxy streamProxy) {
String dstUrl;
if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())) {
@ -463,10 +463,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
MediaInfo mediaInfo = getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream());
if (mediaInfo != null) {
if (mediaInfo.getOriginUrl() != null && mediaInfo.getOriginUrl().equals(streamProxy.getSrcUrl())) {
log.info("[启动拉流代理] 已存在, 直接返回, app {}, stream: {}", mediaInfo.getApp(), streamProxy.getStream());
return getStreamInfoByAppAndStream(mediaServer, streamProxy.getApp(), streamProxy.getStream(), mediaInfo, null, true);
}
closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream());
}
@ -490,15 +486,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
JSONObject data = jsonObject.getJSONObject("data");
if (data == null) {
throw new ControllerException(jsonObject.getInteger("code"), "代理结果异常: " + jsonObject);
}else {
streamProxy.setStreamKey(data.getString("key"));
// 由于此时流未注册手动拼装流信息
mediaInfo = new MediaInfo();
mediaInfo.setApp(streamProxy.getApp());
mediaInfo.setStream(streamProxy.getStream());
mediaInfo.setOriginType(4);
mediaInfo.setOriginTypeStr("pull");
return getStreamInfoByAppAndStream(mediaServer, streamProxy.getApp(), streamProxy.getStream(), mediaInfo, null, true);
}
}
}

View File

@ -378,6 +378,7 @@ public class ZLMRESTfulUtils {
param.put("url", streamUrl);
param.put("timeout_sec", timeout_sec);
param.put("expire_sec", expire_sec);
param.put("async", 1);
sendGetForImg(mediaServerItem, "getSnap", param, targetPath, fileName);
}
@ -446,7 +447,6 @@ public class ZLMRESTfulUtils {
BigDecimal bigDecimal = new BigDecimal(stamp);
param.put("stamp", bigDecimal);
param.put("schema", schema);
System.out.println(bigDecimal);
return sendPost(mediaServer, "seekRecordStamp",param, null);
}
}

View File

@ -28,7 +28,7 @@ public interface IRedisRpcPlayService {
void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback);
StreamInfo playProxy(String serverId, int id);
void playProxy(String serverId, int id, ErrorCallback<StreamInfo> callback);
void stopProxy(String serverId, int id);

View File

@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.service.redisMsg.control;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage;
@ -63,10 +62,13 @@ public class RedisRpcStreamProxyController extends RpcController {
response.setBody("param error");
return response;
}
StreamInfo streamInfo = streamProxyPlayService.startProxy(streamProxy);
response.setStatusCode(ErrorCode.SUCCESS.getCode());
response.setBody(JSONObject.toJSONString(streamInfo));
return response;
streamProxyPlayService.startProxy(streamProxy, (code, msg, streamInfo) -> {
response.setStatusCode(code);
response.setBody(JSONObject.toJSONString(streamInfo));
sendResponse(response);
});
return null;
}
/**

View File

@ -212,13 +212,20 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService {
}
@Override
public StreamInfo playProxy(String serverId, int id) {
public void playProxy(String serverId, int id, ErrorCallback<StreamInfo> callback) {
RedisRpcRequest request = buildRequest("streamProxy/play", id);
request.setToId(serverId);
RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS);
if (response != null && response.getStatusCode() == ErrorCode.SUCCESS.getCode()) {
return JSON.parseObject(response.getBody().toString(), StreamInfo.class);
if (response == null) {
callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null);
}else {
if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) {
StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class);
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
}else {
callback.run(response.getStatusCode(), response.getBody().toString(), null);
}
}
return null;
}
@Override

View File

@ -7,12 +7,15 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService;
import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -20,8 +23,10 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
@ -89,7 +94,7 @@ public class StreamProxyController {
})
@PostMapping(value = "/save")
@ResponseBody
public StreamContent save(@RequestBody StreamProxyParam param){
public DeferredResult<WVPResult<StreamContent>> save(HttpServletRequest request, @RequestBody StreamProxyParam param){
log.info("添加代理: " + JSONObject.toJSONString(param));
if (ObjectUtils.isEmpty(param.getMediaServerId())) {
param.setMediaServerId("auto");
@ -97,18 +102,39 @@ public class StreamProxyController {
if (ObjectUtils.isEmpty(param.getType())) {
param.setType("default");
}
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
if (code == InviteErrorCode.SUCCESS.getCode()) {
WVPResult<StreamContent> wvpResult = WVPResult.success();
if (streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix())
&& !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
StreamInfo streamInfo = streamProxyService.save(param);
if (param.isEnable()) {
if (streamInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
result.setResult(wvpResult);
}else {
return new StreamContent(streamInfo);
result.setResult(WVPResult.fail(code, msg));
}
}else {
return null;
}
};
streamProxyService.save(param, callback);
return result;
}
@Operation(summary = "新增代理", security = @SecurityRequirement(name = JwtUtils.HEADER), parameters = {
@ -193,25 +219,46 @@ public class StreamProxyController {
@ResponseBody
@Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "id", description = "代理Id", required = true)
public StreamContent start(HttpServletRequest request, int id){
public DeferredResult<WVPResult<StreamContent>> start(HttpServletRequest request, int id){
log.info("播放代理: {}", id);
StreamInfo streamInfo = streamProxyPlayService.start(id, null, null);
if (streamInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
}else {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
StreamProxy streamProxy = streamProxyService.getStreamProxy(id);
Assert.notNull(streamProxy, "代理信息不存在");
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
if (code == InviteErrorCode.SUCCESS.getCode()) {
WVPResult<StreamContent> wvpResult = WVPResult.success();
if (streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix())
&& !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
streamInfo.changeStreamIp(host);
result.setResult(wvpResult);
}else {
result.setResult(WVPResult.fail(code, msg));
}
return new StreamContent(streamInfo);
}
};
streamProxyPlayService.start(id, null, callback);
return result;
}
@GetMapping(value = "/stop")

View File

@ -92,5 +92,5 @@ public interface StreamProxyMapper {
" SET pulling=#{pulling}, media_server_id = #{mediaServerId}, " +
" stream_key = #{streamKey} " +
" WHERE id=#{id}")
void addStream(StreamProxy streamProxy);
void updateStream(StreamProxy streamProxy);
}

View File

@ -4,13 +4,13 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import javax.validation.constraints.NotNull;
public interface IStreamProxyPlayService {
StreamInfo start(int id, Boolean record, ErrorCallback<StreamInfo> callback);
void start(int id, Boolean record, ErrorCallback<StreamInfo> callback);
void start(int id, ErrorCallback<StreamInfo> callback);
StreamInfo startProxy(StreamProxy streamProxy);
void startProxy(@NotNull StreamProxy streamProxy, ErrorCallback<StreamInfo> callback);
void stop(int id);

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.streamProxy.service;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
@ -15,7 +16,7 @@ public interface IStreamProxyService {
* 保存视频代理
* @param param
*/
StreamInfo save(StreamProxyParam param);
void save(StreamProxyParam param, ErrorCallback<StreamInfo> callback);
/**
* 分页查询
@ -38,7 +39,7 @@ public interface IStreamProxyService {
* @param stream
* @return
*/
boolean startByAppAndStream(String app, String stream);
void startByAppAndStream(String app, String stream, ErrorCallback<StreamInfo> callback);
/**
* 停用用视频代理

View File

@ -1,36 +1,29 @@
package com.genersoft.iot.vmp.streamProxy.service.impl;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper;
import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyPlayService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import javax.sip.message.Response;
import javax.validation.constraints.NotNull;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 视频代理业务
@ -57,96 +50,42 @@ public class StreamProxyPlayServiceImpl implements IStreamProxyPlayService {
@Autowired
private IRedisRpcPlayService redisRpcPlayService;
private ConcurrentHashMap<Integer, ErrorCallback<StreamInfo>> callbackMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<Integer, StreamInfo> streamInfoMap = new ConcurrentHashMap<>();
/**
* 流到来的处理
*/
@Async("taskExecutor")
@Transactional
@EventListener
public void onApplicationEvent(MediaArrivalEvent event) {
if ("rtsp".equals(event.getSchema())) {
StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(event.getApp(), event.getStream());
if (streamProxy != null) {
ErrorCallback<StreamInfo> callback = callbackMap.remove(streamProxy.getId());
StreamInfo streamInfo = streamInfoMap.remove(streamProxy.getId());
if (callback != null && streamInfo != null) {
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
}
}
}
}
@Override
public void start(int id, ErrorCallback<StreamInfo> callback) {
StreamProxy streamProxy = streamProxyMapper.select(id);
if (streamProxy == null) {
throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
}
StreamInfo streamInfo = startProxy(streamProxy);
if (streamInfo == null) {
callback.run(Response.BUSY_HERE, "busy here", null);
return;
}
callbackMap.put(id, callback);
streamInfoMap.put(id, streamInfo);
MediaServer mediaServer = mediaServerService.getOne(streamProxy.getMediaServerId());
if (mediaServer != null) {
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream());
if (mediaInfo != null) {
callbackMap.remove(id);
streamInfoMap.remove(id);
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
}
}
}
@Override
public StreamInfo start(int id, Boolean record, ErrorCallback<StreamInfo> callback) {
public void start(int id, Boolean record, ErrorCallback<StreamInfo> callback) {
log.info("[拉流代理] 开始拉流ID{}", id);
StreamProxy streamProxy = streamProxyMapper.select(id);
if (streamProxy == null) {
throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
}
log.info("[拉流代理] 类型: {} app{}, stream: {}, 流地址: {}", streamProxy.getType(), streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl());
if (record != null) {
streamProxy.setEnableMp4(record);
}
StreamInfo streamInfo = startProxy(streamProxy);
if (callback != null) {
// 设置流超时的定时任务
String timeOutTaskKey = UUID.randomUUID().toString();
Hook rtpHook = Hook.getInstance(HookType.on_media_arrival, streamProxy.getApp(), streamProxy.getStream(), streamInfo.getMediaServer().getId());
dynamicTask.startDelay(timeOutTaskKey, () -> {
// 收流超时
subscribe.removeSubscribe(rtpHook);
callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), streamInfo);
}, userSetting.getPlayTimeout());
// 开启流到来的监听
subscribe.addSubscribe(rtpHook, (hookData) -> {
dynamicTask.stop(timeOutTaskKey);
// hook响应
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
subscribe.removeSubscribe(rtpHook);
});
}
return streamInfo;
startProxy(streamProxy, callback);
}
@Override
public StreamInfo startProxy(StreamProxy streamProxy){
public void startProxy(@NotNull StreamProxy streamProxy, ErrorCallback<StreamInfo> callback){
if (!streamProxy.isEnable()) {
return null;
callback.run(ErrorCode.ERROR100.getCode(), "代理未启用", null);
return;
}
if (streamProxy.getServerId() == null) {
streamProxy.setServerId(userSetting.getServerId());
}
if (!userSetting.getServerId().equals(streamProxy.getServerId())) {
return redisRpcPlayService.playProxy(streamProxy.getServerId(), streamProxy.getId());
log.info("[拉流代理] 由其他服务{}管理", streamProxy.getServerId());
redisRpcPlayService.playProxy(streamProxy.getServerId(), streamProxy.getId(), callback);
return;
}
if (streamProxy.getMediaServerId() != null) {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(streamProxy.getApp(), streamProxy.getStream(), streamProxy.getMediaServerId(), null, false);
if (streamInfo != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
return;
}
}
MediaServer mediaServer;
@ -159,12 +98,32 @@ public class StreamProxyPlayServiceImpl implements IStreamProxyPlayService {
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), mediaServerId == null?"未找到可用的媒体节点":"未找到节点" + mediaServerId);
}
StreamInfo streamInfo = mediaServerService.startProxy(mediaServer, streamProxy);
// 设置流超时的定时任务
String timeOutTaskKey = UUID.randomUUID().toString();
Hook rtpHook = Hook.getInstance(HookType.on_media_arrival, streamProxy.getApp(), streamProxy.getStream(), mediaServer.getId());
dynamicTask.startDelay(timeOutTaskKey, () -> {
log.info("[拉流代理] 收流超时app{}stream: {}", streamProxy.getApp(), streamProxy.getStream());
// 收流超时
subscribe.removeSubscribe(rtpHook);
callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
}, userSetting.getPlayTimeout());
// 开启流到来的监听
subscribe.addSubscribe(rtpHook, (hookData) -> {
log.info("[拉流代理] 收流成功app{}stream: {}", hookData.getApp(), hookData.getStream());
dynamicTask.stop(timeOutTaskKey);
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, hookData.getApp(), hookData.getStream(), hookData.getMediaInfo(), null);
// hook响应
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
subscribe.removeSubscribe(rtpHook);
});
mediaServerService.startProxy(mediaServer, streamProxy);
if (mediaServerId == null || !mediaServerId.equals(mediaServer.getId())) {
streamProxy.setMediaServerId(mediaServer.getId());
streamProxyMapper.addStream(streamProxy);
streamProxyMapper.updateStream(streamProxy);
}
return streamInfo;
}
@Override

View File

@ -15,6 +15,7 @@ import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxyParam;
@ -109,7 +110,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
// 拉流代理
StreamProxy streamProxyByAppAndStream = getStreamProxyByAppAndStream(event.getApp(), event.getStream());
if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) {
startByAppAndStream(event.getApp(), event.getStream());
startByAppAndStream(event.getApp(), event.getStream(), ((code, msg, data) -> {
log.info("[拉流代理] 自动点播成功, app {} stream: {}", event.getApp(), event.getStream());
}));
}
}
@ -136,7 +139,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Override
@Transactional
public StreamInfo save(StreamProxyParam param) {
public void save(StreamProxyParam param, ErrorCallback<StreamInfo> callback) {
// 兼容旧接口
StreamProxy streamProxyInDb = getStreamProxyByAppAndStream(param.getApp(), param.getStream());
if (streamProxyInDb != null && streamProxyInDb.getPulling() != null && streamProxyInDb.getPulling()) {
@ -159,9 +162,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
if (param.isEnable()) {
return playService.startProxy(streamProxy);
} else {
return null;
playService.startProxy(streamProxy, callback);
}
}
@ -247,13 +248,12 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Override
public boolean startByAppAndStream(String app, String stream) {
public void startByAppAndStream(String app, String stream, ErrorCallback<StreamInfo> callback) {
StreamProxy streamProxy = streamProxyMapper.selectOneByAppAndStream(app, stream);
if (streamProxy == null) {
throw new ControllerException(ErrorCode.ERROR404.getCode(), "代理信息未找到");
}
StreamInfo streamInfo = playService.startProxy(streamProxy);
return streamInfo != null;
playService.startProxy(streamProxy, callback);
}
@Override
@ -406,7 +406,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
streamProxy.setPulling(status);
streamProxy.setMediaServerId(mediaServerId);
streamProxy.setUpdateTime(DateUtil.getNow());
streamProxyMapper.addStream(streamProxy);
streamProxyMapper.updateStream(streamProxy);
streamProxy.setGbStatus(status ? "ON" : "OFF");
if (streamProxy.getGbId() > 0) {

View File

@ -15,7 +15,7 @@ import java.time.temporal.TemporalAccessor;
import java.util.Locale;
/**
/**
* 全局时间工具类
* @author lin
*/
@ -74,7 +74,7 @@ public class DateUtil {
public static String yyyy_MM_dd_HH_mm_ssToISO8601(@NotNull String formatTime) {
return formatterISO8601.format(formatter.parse(formatTime));
}
public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
// 三种日期格式都尝试为了兼容不同厂家的日期格式
if (verification(formatTime, formatterCompatibleISO8601)) {
@ -211,11 +211,4 @@ public class DateUtil {
return ChronoUnit.MILLIS.between(startInstant, endInstant);
}
public static void main(String[] args) {
long difference = getDifference("2025-05-21 13:00:00", "2025-05-21 13:30:00")/1000;
System.out.println(difference);
}
}

View File

@ -100,7 +100,7 @@ public class RecordPlanController {
return recordPlanService.query(page, count, query);
}
@Operation(summary = "分页查询级联平台的所有所有通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Operation(summary = "分页查询录制计划关联的所有通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "page", description = "当前页", required = true)
@Parameter(name = "count", description = "每页条数", required = true)
@Parameter(name = "planId", description = "录制计划ID")

View File

@ -90,4 +90,6 @@ user-settings:
record-sip: true
# 国标点播 按需拉流, true有人观看拉流无人观看释放 false拉起后不自动释放
stream-on-demand: true
# 是否返回Date属性true不返回避免摄像头通过该参数自动校时false返回摄像头可能会根据该时间校时
disable-date-header: false

View File

@ -31,7 +31,7 @@ module.exports = {
"comma-dangle": ["warn", "never"],
"space-in-parens": "warn",
"comma-spacing": "warn",
"object-curly-spacing": "warn",
"object-curly-spacing": ["warn", "always"],
"arrow-spacing": "warn",
semi: ["warn", "never"],
"no-multi-spaces": "warn",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -2,32 +2,6 @@ import request from '@/utils/request'
// 通用通道API
export function update(data) {
return request({
method: 'post',
url: '/api/common/channel/update',
data: data
})
}
export function add(data) {
return request({
method: 'post',
url: '/api/common/channel/add',
data: data
})
}
export function reset(id) {
return request({
method: 'post',
url: '/api/common/channel/reset',
params: {
id: id
}
})
}
export function queryOne(id) {
return request({
method: 'get',
@ -38,180 +12,6 @@ export function queryOne(id) {
})
}
export function addDeviceToGroup(params) {
const { parentId, businessGroup, deviceIds } = params
return request({
method: 'post',
url: `/api/common/channel/group/device/add`,
data: {
parentId: parentId,
businessGroup: businessGroup,
deviceIds: deviceIds
}
})
}
export function addToGroup(params) {
const { parentId, businessGroup, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/group/add`,
data: {
parentId: parentId,
businessGroup: businessGroup,
channelIds: channelIds
}
})
}
export function deleteDeviceFromGroup(deviceIds) {
return request({
method: 'post',
url: `/api/common/channel/group/device/delete`,
data: {
deviceIds: deviceIds
}
})
}
export function deleteFromGroup(channels) {
return request({
method: 'post',
url: `/api/common/channel/group/delete`,
data: {
channelIds: channels
}
})
}
export function addDeviceToRegion(params) {
const { civilCode, deviceIds } = params
return request({
method: 'post',
url: `/api/common/channel/region/device/add`,
data: {
civilCode: civilCode,
deviceIds: deviceIds
}
})
}
export function addToRegion(params) {
const { civilCode, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/region/add`,
data: {
civilCode: civilCode,
channelIds: channelIds
}
})
}
export function deleteDeviceFromRegion(deviceIds) {
return request({
method: 'post',
url: `/api/common/channel/region/device/delete`,
data: {
deviceIds: deviceIds
}
})
}
export function deleteFromRegion(channels) {
return request({
method: 'post',
url: `/api/common/channel/region/delete`,
data: {
channelIds: channels
}
})
}
export function getCivilCodeList(params) {
const { page, count, channelType, query, online, civilCode } = params
return request({
method: 'get',
url: `/api/common/channel/civilcode/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online,
civilCode: civilCode
}
})
}
export function getParentList(params) {
const { page, count, channelType, query, online, groupDeviceId } = params
return request({
method: 'get',
url: `/api/common/channel/parent/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online,
groupDeviceId: groupDeviceId
}
})
}
export function getUnusualParentList(params) {
const { page, count, channelType, query, online } = params
return request({
method: 'get',
url: `/api/common/channel/parent/unusual/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online
}
})
}
export function clearUnusualParentList(params) {
const { all, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/parent/unusual/clear`,
data: {
all: all,
channelIds: channelIds
}
})
}
export function getUnusualCivilCodeList(params) {
const { page, count, channelType, query, online } = params
return request({
method: 'get',
url: `/api/common/channel/civilCode/unusual/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online
}
})
}
export function clearUnusualCivilCodeList(params) {
const { all, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/civilCode/unusual/clear`,
data: {
all: all,
channelIds: channelIds
}
})
}
export function getIndustryList() {
return request({
method: 'get',
@ -233,6 +33,224 @@ export function getNetworkIdentificationList() {
})
}
export function update(data) {
return request({
method: 'post',
url: '/api/common/channel/update',
data: data
})
}
export function reset(id) {
return request({
method: 'post',
url: '/api/common/channel/reset',
params: {
id: id
}
})
}
export function add(data) {
return request({
method: 'post',
url: '/api/common/channel/add',
data: data
})
}
export function getList(params) {
const { page, count, query, online, hasRecordPlan, channelType } = params
return request({
method: 'get',
url: `/api/common/channel/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online,
hasRecordPlan: hasRecordPlan
}
})
}
export function getCivilCodeList(params) {
const { page, count, channelType, query, online, civilCode } = params
return request({
method: 'get',
url: `/api/common/channel/civilcode/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online,
civilCode: civilCode
}
})
}
export function getUnusualCivilCodeList(params) {
const { page, count, channelType, query, online } = params
return request({
method: 'get',
url: `/api/common/channel/civilCode/unusual/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online
}
})
}
export function getUnusualParentList(params) {
const { page, count, channelType, query, online } = params
return request({
method: 'get',
url: `/api/common/channel/parent/unusual/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online
}
})
}
export function clearUnusualCivilCodeList(params) {
const { all, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/civilCode/unusual/clear`,
data: {
all: all,
channelIds: channelIds
}
})
}
export function clearUnusualParentList(params) {
const { all, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/parent/unusual/clear`,
data: {
all: all,
channelIds: channelIds
}
})
}
export function getParentList(params) {
const { page, count, channelType, query, online, groupDeviceId } = params
return request({
method: 'get',
url: `/api/common/channel/parent/list`,
params: {
page: page,
count: count,
channelType: channelType,
query: query,
online: online,
groupDeviceId: groupDeviceId
}
})
}
export function addToRegion(params) {
const { civilCode, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/region/add`,
data: {
civilCode: civilCode,
channelIds: channelIds
}
})
}
export function deleteFromRegion(channels) {
return request({
method: 'post',
url: `/api/common/channel/region/delete`,
data: {
channelIds: channels
}
})
}
export function addDeviceToRegion(params) {
const { civilCode, deviceIds } = params
return request({
method: 'post',
url: `/api/common/channel/region/device/add`,
data: {
civilCode: civilCode,
deviceIds: deviceIds
}
})
}
export function deleteDeviceFromRegion(deviceIds) {
return request({
method: 'post',
url: `/api/common/channel/region/device/delete`,
data: {
deviceIds: deviceIds
}
})
}
export function addToGroup(params) {
const { parentId, businessGroup, channelIds } = params
return request({
method: 'post',
url: `/api/common/channel/group/add`,
data: {
parentId: parentId,
businessGroup: businessGroup,
channelIds: channelIds
}
})
}
export function deleteFromGroup(channels) {
return request({
method: 'post',
url: `/api/common/channel/group/delete`,
data: {
channelIds: channels
}
})
}
export function addDeviceToGroup(params) {
const { parentId, businessGroup, deviceIds } = params
return request({
method: 'post',
url: `/api/common/channel/group/device/add`,
data: {
parentId: parentId,
businessGroup: businessGroup,
deviceIds: deviceIds
}
})
}
export function deleteDeviceFromGroup(deviceIds) {
return request({
method: 'post',
url: `/api/common/channel/group/device/delete`,
data: {
deviceIds: deviceIds
}
})
}
export function playChannel(channelId) {
return request({
method: 'get',

View File

@ -118,6 +118,19 @@ export function queryChannels(deviceId, params) {
})
}
export function queryHasStreamChannels(params) {
const {page, count, query} = params
return request({
method: 'get',
url: `/api/device/query/streams`,
params: {
page: page,
count: count,
query: query
}
})
}
export function deviceRecord(params) {
const { deviceId, channelId, recordCmdStr } = params
return request({

View File

@ -48,3 +48,15 @@ export function getPath(params) {
}
})
}
export function queryTree(params) {
const { page, count, query } = params
return request({
method: 'get',
url: `/api/group/tree/query`,
params: {
query: query,
page: page,
count: count
}
})
}

View File

@ -81,4 +81,16 @@ export function queryPath(deviceId) {
}
})
}
export function queryTree(params) {
const { page, count, query } = params
return request({
method: 'get',
url: `/api/region/tree/query`,
params: {
query: query,
page: page,
count: count
}
})
}

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751337772774" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19666" id="mx_n_1751337772775" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M793.6 128a38.4 38.4 0 0 1 38.4 38.4v46.912h110.912a38.4 38.4 0 0 1 38.4 38.4V806.4a38.4 38.4 0 0 1-38.4 38.4L832 844.736v46.976a38.4 38.4 0 0 1-38.4 38.4H238.912a38.4 38.4 0 0 1-38.4-38.4V844.8L89.6 844.8a38.4 38.4 0 0 1-38.4-38.4V251.712a38.4 38.4 0 0 1 38.4-38.4h110.912V166.4a38.4 38.4 0 0 1 38.4-38.4H793.6z m-38.4 76.8H277.248v648.512H755.2V204.8zM200.448 290.112H128V768h72.512V290.112z m704 0H832V768h72.512V290.112z m-443.392 103.04l192 128a35.2 35.2 0 0 1 0 58.56l-192 128a35.2 35.2 0 0 1-54.72-29.312v-256c0-28.16 31.36-44.864 54.72-29.312z m15.68 94.976v124.48L570.112 550.4 476.8 488.128z" p-id="19667"></path></svg>

After

Width:  |  Height:  |  Size: 964 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751336642026" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4525" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M185.6 723.2v89.6h652.8v-89.6H185.6z m-6.4-64h665.6c31.7952 0 57.6 25.8048 57.6 57.6v102.4c0 31.7952-25.8048 57.6-57.6 57.6h-665.6A57.6 57.6 0 0 1 121.6 819.2v-102.4c0-31.7952 25.8048-57.6 57.6-57.6zM185.6 211.2v294.4h448v-294.4h-448zM179.2 147.2h460.8c31.7952 0 57.6 25.8048 57.6 57.6v307.2c0 31.7952-25.8048 57.6-57.6 57.6h-460.8A57.6 57.6 0 0 1 121.6 512V204.8c0-31.7952 25.8048-57.6 57.6-57.6z" p-id="4526"></path><path d="M697.6 415.4368l140.8 70.4V230.912l-140.8 70.4v114.0736z m121.4464-246.3232a57.6 57.6 0 0 1 83.3536 51.5072v275.5584a57.6 57.6 0 0 1-83.3536 51.5072l-185.4464-92.672V261.7856l185.4464-92.7232z" fill="#5A5A68" p-id="4527"></path></svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@ -17,10 +17,10 @@
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">Refresh</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li>
<li @click="closeOthersTags">Close Others</li>
<li @click="closeAllTags(selectedTag)">Close All</li>
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
<li @click="closeOthersTags">关闭其他</li>
<li @click="closeAllTags(selectedTag)">关闭所有</li>
</ul>
</div>
</template>

View File

@ -66,14 +66,25 @@ export const constantRoutes = [
meta: { title: '分屏监控', icon: 'live' }
}]
},
{
path: '/channel',
component: Layout,
redirect: '/channel',
children: [{
path: '',
name: 'Channel',
component: () => import('@/views/channel/index'),
meta: {title: '通道列表', icon: 'channelManger'}
}]
},
{
path: '/device',
component: Layout,
redirect: '/device',
onlyIndex: 0,
name: '设备接入',
meta: { title: '设备接入', icon: 'devices' },
children: [
{
path: '',
path: '/device',
name: 'Device',
component: () => import('@/views/device/index'),
meta: { title: '国标设备', icon: 'device' }
@ -82,50 +93,34 @@ export const constantRoutes = [
path: '/device/record/:deviceId/:channelDeviceId',
name: 'DeviceRecord',
component: () => import('@/views/device/channel/record'),
meta: { title: '国标录像' }
}
]
},
{
path: '/jtDevice',
component: Layout,
redirect: '/jtDevice',
onlyIndex: 0,
children: [
{
path: '',
name: 'JTDevice',
component: () => import('@/views/jtDevice/index'),
meta: { title: '部标设备', icon: 'jtDevice' }
},
{
path: '/jtDevice/record/:phoneNumber/:channelId',
name: 'JTDeviceRecord',
component: () => import('@/views/jtDevice/channel/record'),
meta: { title: '部标录像' }
}
]
},
{
path: '/push',
component: Layout,
redirect: '/push',
children: [
path: '/jtDevice',
component: Layout,
redirect: '/jtDevice',
onlyIndex: 0,
children: [
{
path: '',
name: 'JTDevice',
component: () => import('@/views/jtDevice/index'),
meta: { title: '部标设备', icon: 'jtDevice' }
},
{
path: '/jtDevice/record/:phoneNumber/:channelId',
name: 'JTDeviceRecord',
component: () => import('@/views/jtDevice/channel/record'),
}
]
},
{
path: '',
path: '/push',
name: 'PushList',
component: () => import('@/views/streamPush/index'),
meta: { title: '推流列表', icon: 'streamPush' }
}
]
},
{
path: '/proxy',
component: Layout,
redirect: '/proxy',
children: [
},
{
path: '',
path: '/proxy',
name: 'Proxy',
component: () => import('@/views/streamProxy/index'),
meta: { title: '拉流代理', icon: 'streamProxy' }
@ -136,8 +131,8 @@ export const constantRoutes = [
path: '/commonChannel',
component: Layout,
redirect: '/commonChannel/region',
name: '通道管理',
meta: { title: '通道管理', icon: 'channelManger' },
name: '组织结构',
meta: { title: '组织结构', icon: 'tree' },
children: [
{
path: 'region',

View File

@ -15,7 +15,22 @@ import {
clearUnusualCivilCodeList,
getIndustryList,
getTypeList,
getNetworkIdentificationList, playChannel, addToRegion, deleteFromRegion, addToGroup, deleteFromGroup
getNetworkIdentificationList, playChannel, addToRegion, deleteFromRegion, addToGroup, deleteFromGroup, getList,
addPointForCruise,
addPreset, auxiliary,
callPreset,
deletePointForCruise,
deletePreset, focus, iris, ptz,
queryPreset,
setCruiseSpeed,
setCruiseTime,
setLeftForScan,
setRightForScan,
setSpeedForScan,
startCruise,
startScan,
stopCruise,
stopScan, wiper, getAllForMap
} from '@/api/commonChannel'
const actions = {
@ -238,6 +253,226 @@ const actions = {
reject(error)
})
})
},
getList({ commit }, param) {
return new Promise((resolve, reject) => {
getList(param).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setSpeedForScan({ commit }, params) {
return new Promise((resolve, reject) => {
setSpeedForScan(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setLeftForScan({ commit }, params) {
return new Promise((resolve, reject) => {
setLeftForScan(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setRightForScan({ commit }, params) {
return new Promise((resolve, reject) => {
setRightForScan(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
startScan({ commit }, params) {
return new Promise((resolve, reject) => {
startScan(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
stopScan({ commit }, params) {
return new Promise((resolve, reject) => {
stopScan(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
addPointForCruise({ commit }, params) {
return new Promise((resolve, reject) => {
addPointForCruise(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
deletePointForCruise({ commit }, params) {
return new Promise((resolve, reject) => {
deletePointForCruise(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setCruiseSpeed({ commit }, params) {
return new Promise((resolve, reject) => {
setCruiseSpeed(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setCruiseTime({ commit }, params) {
return new Promise((resolve, reject) => {
setCruiseTime(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
startCruise({ commit }, params) {
return new Promise((resolve, reject) => {
startCruise(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
stopCruise({ commit }, params) {
return new Promise((resolve, reject) => {
stopCruise(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
addPreset({ commit }, params) {
return new Promise((resolve, reject) => {
addPreset(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
queryPreset({ commit }, params) {
return new Promise((resolve, reject) => {
queryPreset(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
callPreset({ commit }, params) {
return new Promise((resolve, reject) => {
callPreset(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
deletePreset({ commit }, params) {
return new Promise((resolve, reject) => {
deletePreset(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
auxiliary({ commit }, params) {
return new Promise((resolve, reject) => {
auxiliary(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
wiper({ commit }, params) {
return new Promise((resolve, reject) => {
wiper(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
ptz({ commit }, params) {
return new Promise((resolve, reject) => {
ptz(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
iris({ commit }, params) {
return new Promise((resolve, reject) => {
iris(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
focus({ commit }, params) {
return new Promise((resolve, reject) => {
focus(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
getAllForMap({ commit }, params) {
return new Promise((resolve, reject) => {
getAllForMap(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}

View File

@ -1,17 +1,24 @@
import {
add, changeChannelAudio,
add,
changeChannelAudio,
deleteDevice,
deviceRecord,
queryBasicParam,
queryChannelOne,
queryChannels, queryChannelTree, queryDeviceOne,
queryChannels,
queryChannelTree,
queryDeviceOne,
queryDevices,
queryDeviceSyncStatus, queryDeviceTree,
queryDeviceSyncStatus,
queryDeviceTree,
queryHasStreamChannels,
resetGuard,
setGuard,
subscribeCatalog,
subscribeMobilePosition,
sync, update, updateChannelStreamIdentification,
sync,
update,
updateChannelStreamIdentification,
updateDeviceTransport
} from '@/api/device'
@ -126,6 +133,16 @@ const actions = {
})
})
},
queryHasStreamChannels({commit}, params) {
return new Promise((resolve, reject) => {
queryHasStreamChannels(params).then(response => {
const {data} = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
deviceRecord({ commit }, params) {
return new Promise((resolve, reject) => {
deviceRecord(params).then(response => {

View File

@ -1,7 +1,7 @@
import {
getTreeList,
update,
add, deleteGroup, getPath
add, deleteGroup, getPath, queryTree
} from '@/api/group'
const actions = {
@ -54,6 +54,16 @@ const actions = {
reject(error)
})
})
},
queryTree({ commit }, param) {
return new Promise((resolve, reject) => {
queryTree(param).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}

View File

@ -6,7 +6,7 @@ import {
queryChildListInBase,
update,
add,
queryPath
queryPath, queryTree
} from '@/api/region'
const actions = {
@ -89,6 +89,16 @@ const actions = {
reject(error)
})
})
},
queryTree({ commit }, param) {
return new Promise((resolve, reject) => {
queryTree(param).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}

View File

@ -3,6 +3,7 @@ import Cookies from 'js-cookie'
const TokenKey = 'wvp_token'
const NameKey = 'wvp_username'
const serverIdKey = 'wvp_server_id'
const expires = 30
export function getToken() {
console.log('Getting token...')
@ -10,7 +11,7 @@ export function getToken() {
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
return Cookies.set(TokenKey, token, {expires: expires})
}
export function removeToken() {
@ -22,7 +23,7 @@ export function getName() {
}
export function setName(name) {
return Cookies.set(NameKey, name)
return Cookies.set(NameKey, name, {expires: expires})
}
export function removeName() {
@ -34,7 +35,7 @@ export function getServerId() {
}
export function setServerId(serverId) {
return Cookies.set(serverIdKey, serverId)
return Cookies.set(serverIdKey, serverId, {expires: expires})
}
export function removeServerId() {

View File

@ -0,0 +1,30 @@
<template>
<div id="ChannelEdit" v-loading="locading" style="width: 100%">
<div class="page-header">
<div class="page-title">
<el-page-header content="编辑通道" @back="close" />
</div>
</div>
<CommonChannelEdit :id="id" ref="commonChannelEdit" :save-success="close" :cancel="close" />
</div>
</template>
<script>
import CommonChannelEdit from '../common/CommonChannelEdit'
export default {
name: 'ChannelEdit',
components: {
CommonChannelEdit
},
props: ['id', 'closeEdit'],
data() {
return {}
},
methods: {
close: function() {
this.closeEdit()
}
}
}
</script>

View File

@ -4,7 +4,7 @@
ref="groupTree"
:show-header="true"
:edit="true"
:click-event="treeNodeClickEvent"
@clickEvent="treeNodeClickEvent"
:on-channel-change="onChannelChange"
:enable-add-channel="true"
:add-channel-to-group="addChannelToGroup"
@ -20,7 +20,7 @@
<div style="float: right;">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: 10rem; "
placeholder="关键字"
prefix-icon="el-icon-search"
@ -132,7 +132,7 @@ export default {
data() {
return {
channelList: [],
searchSrt: '',
searchStr: '',
channelType: '',
online: '',
hasGroup: 'false',
@ -170,7 +170,7 @@ export default {
this.$store.dispatch('commonChanel/getParentList', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
online: this.online,
channelType: this.channelType,
groupDeviceId: this.groupDeviceId

439
web/src/views/channel/index.vue Executable file
View File

@ -0,0 +1,439 @@
<template>
<div id="channelList" class="app-container" style="height: calc(100vh - 124px);">
<div v-if="!editId" style="height: 100%">
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
placeholder="关键字"
prefix-icon="el-icon-search"
clearable
@input="search"
/>
</el-form-item>
<el-form-item label="在线状态">
<el-select
v-model="online"
style="width: 8rem; margin-right: 1rem;"
placeholder="请选择"
default-first-option
@change="search"
>
<el-option label="全部" value="" />
<el-option label="在线" value="true" />
<el-option label="离线" value="false" />
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select
v-model="channelType"
style="width: 8rem; margin-right: 1rem;"
placeholder="请选择"
default-first-option
@change="getChannelList"
>
<el-option label="全部" value="" />
<el-option v-for="item in Object.values($channelTypeList)" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item style="float: right;">
<el-button icon="el-icon-refresh-right" circle @click="refresh()" title="刷新表格"/>
</el-form-item>
</el-form>
<el-table
ref="channelListTable"
size="small"
:data="channelList"
height="calc(100% - 64px)"
style="width: 100%; font-size: 12px;"
header-row-class-name="table-header"
>
<el-table-column prop="gbName" label="名称" min-width="180" />
<el-table-column prop="gbDeviceId" label="编号" min-width="180" />
<el-table-column prop="gbManufacturer" label="厂家" min-width="100" />
<el-table-column label="类型" min-width="100">
<template v-slot:default="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium" effect="plain" type="success" :style="$channelTypeList[scope.row.dataType].style">{{ $channelTypeList[scope.row.dataType].name }}</el-tag>
</div>
</template>
</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 prop="ptzType" label="云台类型" min-width="100">
<template v-slot:default="scope">
<div>{{ scope.row.ptzTypeText }}</div>
</template>
</el-table-column>
<el-table-column label="状态" min-width="100">
<template v-slot:default="scope">
<div slot="reference" class="name-wrapper">
<el-tag v-if="scope.row.gbStatus === 'ON'" size="medium">在线</el-tag>
<el-tag v-if="scope.row.gbStatus !== 'ON'" size="medium" type="info">离线</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" min-width="210" fixed="right">
<template v-slot:default="scope">
<el-button
size="medium"
:disabled="scope.row.gbStatus !== 'ON'"
icon="el-icon-video-play"
type="text"
:loading="scope.row.playLoading"
@click="sendDevicePush(scope.row)"
>播放
</el-button>
<el-button
v-if="!!scope.row.streamId"
size="medium"
:disabled="device == null || device.online === 0"
icon="el-icon-switch-button"
type="text"
style="color: #f56c6c"
@click="stopDevicePush(scope.row)"
>停止
</el-button>
<el-divider direction="vertical" />
<el-button
size="medium"
type="text"
icon="el-icon-edit"
v-if="$store.getters.authority !== 2"
@click="handleEdit(scope.row)"
>
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="text-align: right"
:current-page="currentPage"
:page-size="count"
:page-sizes="[15, 25, 35, 50]"
layout="total, sizes, prev, pager, next"
:total="total"
@size-change="handleSizeChange"
@current-change="currentChange"
/>
</div>
<devicePlayer ref="devicePlayer" />
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
</div>
</template>
<script>
import devicePlayer from '@/views/common/channelPlayer/index.vue'
import Edit from './edit.vue'
export default {
name: 'ChannelList',
components: {
devicePlayer,
ChannelEdit: Edit
},
props: {
defaultPage: {
type: Number,
default: 1
},
defaultCount: {
type: Number,
default: 15
}
},
computed: {
excelName(){
return '通道列表-' + this.currentPage
}
},
data() {
return {
device: null,
channelList: [],
excelFields: {
名称: 'gbName',
编号: 'gbDeviceId',
厂家: 'gbManufacturer',
类型: {
field: 'dataType',
callback: (value) => {
return this.$channelTypeList[value].name
}
},
经度: 'gbLongitude',
纬度: 'gbLatitude',
云台类型: 'ptzTypeText',
状态: {
field: 'gbStatus',
callback: (value) => {
return value === 'ON' ? '在线' : '离线'
}
}
},
videoComponentList: [],
currentPlayerInfo: {}, //
updateLooper: 0, //
searchStr: '',
channelType: '',
online: '',
subStream: '',
winHeight: window.innerHeight - 200,
currentPage: this.defaultPage | 1,
count: this.defaultCount | 15,
total: 0,
beforeUrl: '/device',
editId: null,
}
},
mounted() {
this.initData()
},
destroyed() {
this.$destroy('videojs')
clearTimeout(this.updateLooper)
},
methods: {
initData: function() {
this.getChannelList()
},
initParam: function() {
this.currentPage = 1
this.count = 15
},
currentChange: function(val) {
this.currentPage = val
this.initData()
},
handleSizeChange: function(val) {
this.count = val
this.getChannelList()
},
getChannelList: function() {
this.$store.dispatch('commonChanel/getList', {
page: this.currentPage,
count: this.count,
query: this.searchStr,
online: this.online,
channelType: this.channelType
}).then(data => {
this.total = data.total
this.channelList = data.list
this.channelList.forEach(e => {
e.ptzType = e.ptzType + ''
this.$set(e, 'playLoading', false)
})
//
this.$nextTick(() => {
this.$refs.channelListTable.doLayout()
})
})
},
//
sendDevicePush: function(itemData) {
itemData.playLoading = true
this.$store.dispatch('commonChanel/playChannel', itemData.gbId)
.then((data) => {
itemData.streamId = data.stream
this.$refs.devicePlayer.openDialog('media', itemData.gbId, {
streamInfo: data,
hasAudio: itemData.hasAudio
})
setTimeout(() => {
this.initData()
}, 1000)
}).finally(() => {
itemData.playLoading = false
})
},
queryRecords: function(itemData) {
const deviceId = this.deviceId
const channelId = itemData.deviceId
this.$router.push(`/device/record/${deviceId}/${channelId}`)
},
queryCloudRecords: function(itemData) {
const deviceId = this.deviceId
const channelId = itemData.deviceId
this.$router.push(`/cloudRecord/detail/rtp/${deviceId}_${channelId}`)
},
startRecord: function(itemData) {
this.$store.dispatch('device/deviceRecord', {
deviceId: this.deviceId,
channelId: itemData.deviceId,
recordCmdStr: 'Record'
}).then(data => {
this.$message.success({
showClose: true,
message: '开始录像成功'
})
}).catch((error) => {
this.$message.error({
showClose: true,
message: error.message
})
})
},
stopRecord: function(itemData) {
this.$store.dispatch('device/deviceRecord', {
deviceId: this.deviceId,
channelId: itemData.deviceId,
recordCmdStr: 'StopRecord'
}).then(data => {
this.$message.success({
showClose: true,
message: '停止录像成功'
})
}).catch((error) => {
this.$message.error({
showClose: true,
message: error.message
})
})
},
stopDevicePush: function(itemData) {
this.$store.dispatch('play/stop', [itemData.deviceId]).then(data => {
this.initData()
}).catch((error) => {
if (error.response.status === 402) { //
this.initData()
} else {
console.log(error)
}
})
},
getSnap: function(row) {
const baseUrl = window.baseUrl ? window.baseUrl : ''
return ((process.env.NODE_ENV === 'development') ? process.env.VUE_APP_BASE_API : baseUrl) + '/api/device/query/snap/' + this.deviceId + '/' + row.deviceId
},
getBigSnap: function(row) {
return [this.getSnap(row)]
},
getSnapErrorEvent: function(deviceId, channelId) {
if (typeof (this.loadSnap[deviceId + channelId]) !== 'undefined') {
console.log('下载截图' + this.loadSnap[deviceId + channelId])
if (this.loadSnap[deviceId + channelId] > 5) {
delete this.loadSnap[deviceId + channelId]
return
}
setTimeout(() => {
const url = (process.env.NODE_ENV === 'development' ? 'debug' : '') + '/api/device/query/snap/' + deviceId + '/' + channelId
this.loadSnap[deviceId + channelId]++
document.getElementById(deviceId + channelId).setAttribute('src', url + '?' + new Date().getTime())
}, 1000)
}
},
showDevice: function() {
// this.$router.push(this.beforeUrl).then(() => {
// this.initParam()
// this.initData()
// })
this.$emit('show-device')
},
changeSubchannel(itemData) {
this.beforeUrl = this.$router.currentRoute.path
var url = `/${this.$router.currentRoute.name}/${this.$router.currentRoute.params.deviceId}/${itemData.deviceId}`
this.$router.push(url).then(() => {
this.searchStr = ''
this.channelType = ''
this.online = ''
this.initParam()
this.initData()
})
},
showSubChannels: function() {
this.$store.dispatch('device/querySubChannels', [
{
page: this.currentPage,
count: this.count,
query: this.searchStr,
online: this.online,
channelType: this.channelType
},
this.deviceId,
this.parentChannelId
])
.then(data => {
this.total = data.total
this.channelList = data.list
this.channelList.forEach(e => {
e.ptzType = e.ptzType + ''
})
//
this.$nextTick(() => {
this.$refs.channelListTable.doLayout()
})
})
},
search: function() {
this.currentPage = 1
this.total = 0
this.initData()
},
updateChannel: function(row) {
this.$store.dispatch('device/changeChannelAudio', {
channelId: row.gbId,
audio: row.hasAudio
})
},
subStreamChange: function() {
this.$confirm('确定重置所有通道的码流类型?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('device/updateChannelStreamIdentification', {
deviceDbId: this.device.id,
streamIdentification: this.subStream
})
.then(data => {
this.initData()
})
.finally(() => {
this.subStream = ''
})
}).catch(() => {
this.subStream = ''
})
},
channelSubStreamChange: function(row) {
this.$store.dispatch('device/updateChannelStreamIdentification', {
deviceDbId: row.deviceDbId,
id: row.id,
streamIdentification: row.streamIdentification
})
.then(data => {
this.initData()
})
.finally(() => {
this.subStream = ''
})
},
refresh: function() {
this.initData()
},
//
handleEdit(row) {
console.log(row)
this.editId = row.gbId
},
//
closeEdit: function() {
this.editId = null
this.getChannelList()
}
}
}
</script>

View File

@ -4,7 +4,7 @@
ref="regionTree"
:show-header="true"
:edit="true"
:click-event="treeNodeClickEvent"
@clickEvent="treeNodeClickEvent"
:on-channel-change="onChannelChange"
:enable-add-channel="true"
:add-channel-to-civil-code="addChannelToCivilCode"
@ -20,7 +20,7 @@
<div style="float: right;">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="width: 10rem; margin-right: 1rem;"
placeholder="关键字"
prefix-icon="el-icon-search"
@ -130,7 +130,7 @@ export default {
data() {
return {
channelList: [],
searchSrt: '',
searchStr: '',
channelType: '',
online: '',
currentPage: 1,
@ -166,7 +166,7 @@ export default {
this.$store.dispatch('commonChanel/getCivilCodeList', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
online: this.online,
channelType: this.channelType,
civilCode: this.regionDeviceId

View File

@ -1,5 +1,5 @@
<template>
<div id="DeviceTree" class="device-tree-container">
<div id="DeviceTree" class="device-tree-container" style="height: 100%">
<div class="device-tree-header">
<div class="header-title">通道列表</div>
<div class="header-switch">
@ -20,7 +20,7 @@
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
@clickEvent="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
<GroupTree
@ -29,7 +29,7 @@
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
@clickEvent="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
</div>
@ -53,10 +53,6 @@ export default {
type: Boolean,
default: false
},
clickEvent: {
type: Function,
default: null
},
contextMenuEvent: {
type: Function,
default: null
@ -137,11 +133,7 @@ export default {
},
treeNodeClickEvent: function(data) {
if (data.leaf) {
console.log(23111)
console.log(data)
if (this.clickEvent) {
this.clickEvent(data.id)
}
this.$emit('clickEvent', data.id)
}
}
}

View File

@ -1,24 +1,14 @@
<template>
<div id="DeviceTree" style="border-right: 1px solid #EBEEF5; padding: 0 20px">
<div v-if="showHeader" class="page-header">
<el-form :inline="true" size="mini">
<el-form-item style="visibility: hidden">
<el-input
v-model="searchSrt"
style="margin-right: 1rem; width: 12rem;"
size="mini"
placeholder="关键字"
prefix-icon="el-icon-search"
clearable
@input="search"
/>
</el-form-item>
<el-form-item label="显示编号">
<el-checkbox v-model="showCode" />
</el-form-item>
</el-form>
<div id="groupTree" style="border-right: 1px solid #EBEEF5; height: 100%">
<div style="padding: 0 20px 0 10px;">
<el-input size="small" v-model="searchStr" @input="searchChange" suffix-icon="el-icon-search" placeholder="请输入搜索内容" clearable>
<!-- <el-select v-model="searchType" slot="prepend" placeholder="搜索类型" style="width: 80px">-->
<!-- <el-option label="目录" :value="0"></el-option>-->
<!-- <el-option label="通道" :value="1"></el-option>-->
<!-- </el-select>-->
</el-input>
</div>
<div>
<div v-if="!searchStr">
<el-alert
v-if="showAlert && edit"
title="操作提示"
@ -26,6 +16,10 @@
type="info"
style="text-align: left"
/>
<div v-if="edit" style="float: right;margin-right: 24px;margin-top: 18px; font-size: 14px" >
显示编号 <el-checkbox v-model="showCode" />
</div>
<vue-easy-tree
ref="veTree"
class="flow-tree"
@ -75,6 +69,49 @@
</template>
</vue-easy-tree>
</div>
<div v-if="searchStr" style="color: #606266; height: calc(100% - 32px); overflow: auto !important;">
<ul v-if="groupList.length > 0" style="list-style: none; margin: 0; padding: 10px">
<li v-for="item in groupList" :key="item.id" class="channel-list-li" style="height: 26px; align-items: center;cursor: pointer;" @click="listClickHandler(item)">
<span
v-if="chooseId !== item.deviceId"
style="color: #409EFF; font-size: 20px"
class="iconfont icon-bianzubeifen3"
/>
<span
v-if="chooseId === item.deviceId"
style="color: #c60135; font-size: 20px"
class="iconfont icon-bianzubeifen3"
/>
<div>
<div style="margin-left: 4px; margin-bottom: 3px; font-size: 15px">{{item.name}}</div>
<div style="margin-left: 4px; font-size: 13px; color: #808181">{{item.deviceId}}</div>
</div>
</li>
</ul>
<ul v-if="channelList.length > 0" style="list-style: none; margin: 0; padding: 10px; overflow: auto">
<li v-for="item in channelList" :key="item.id" class="channel-list-li" @click="channelLstClickHandler(item)">
<span
v-if="item.gbStatus === 'ON'"
style="color: #409EFF; font-size: 20px"
class="iconfont icon-shexiangtou2"
/>
<span
v-if="item.gbStatus !== 'ON'"
style="color: #808181; font-size: 20px"
class="iconfont icon-shexiangtou2"
/>
<div>
<div style="margin-left: 4px; margin-bottom: 3px; font-size: 15px">{{item.gbName}}</div>
<div style="margin-left: 4px; font-size: 13px; color: #808181">{{item.gbDeviceId}}</div>
</div>
</li>
</ul>
<div v-if="this.currentPage * this.count < this.total" style="text-align: center;">
<el-button type="text" @click="loadListMore">加载更多</el-button>
</div>
</div>
<groupEdit ref="groupEdit" />
<gbDeviceSelect ref="gbDeviceSelect" />
<gbChannelSelect ref="gbChannelSelect" data-type="group" />
@ -93,7 +130,7 @@ export default {
GbChannelSelect,
VueEasyTree, groupEdit, gbDeviceSelect
},
props: ['edit', 'enableAddChannel', 'clickEvent', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToGroup', 'treeHeight'],
props: ['edit', 'enableAddChannel', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToGroup', 'treeHeight'],
data() {
return {
props: {
@ -102,9 +139,14 @@ export default {
},
showCode: false,
showAlert: true,
searchSrt: '',
searchStr: '',
chooseId: '',
treeData: []
treeData: [],
currentPage: this.defaultPage | 1,
count: this.defaultCount | 15,
total: 0,
groupList: [],
channelList: []
}
},
created() {
@ -118,8 +160,44 @@ export default {
// this.performance = "";
},
methods: {
search() {
searchChange() {
this.currentPage = 1
this.total = 0
if (this.edit) {
this.groupList = []
this.queryGroup()
}else {
this.channelList = []
this.queryChannelList()
}
},
loadListMore: function() {
this.currentPage += 1
if (this.edit) {
this.queryGroup()
}else {
this.queryChannelList()
}
},
queryGroup: function() {
this.$store.dispatch('group/queryTree', {
query: this.searchStr,
page: this.currentPage,
count: this.count
}).then(data => {
this.total = data.total
this.groupList = this.groupList.concat(data.list)
})
},
queryChannelList: function() {
this.$store.dispatch('commonChanel/getList', {
page: this.currentPage,
count: this.count,
query: this.searchStr
}).then(data => {
this.total = data.total
this.channelList = this.channelList.concat(data.list)
})
},
loadNode: function(node, resolve) {
if (node.level === 0) {
@ -136,7 +214,7 @@ export default {
return
}
this.$store.dispatch('group/getTreeList', {
query: this.searchSrt,
query: this.searchStr,
parent: node.data.id,
hasChannel: this.hasChannel
}).then(data => {
@ -144,6 +222,8 @@ export default {
this.showAlert = false
}
resolve(data)
}).finally(() => {
this.locading = false
})
}
},
@ -348,9 +428,17 @@ export default {
},
nodeClickHandler: function(data, node, tree) {
this.chooseId = data.deviceId
if (this.clickEvent) {
this.clickEvent(data)
}
this.$emit('clickEvent', data)
},
listClickHandler: function(data) {
this.chooseId = data.deviceId
this.$emit('clickEvent', data)
},
channelLstClickHandler: function(data) {
this.$emit('clickEvent', {
leaf: true,
id: data.gbId
})
}
}
}
@ -375,7 +463,7 @@ export default {
.flow-tree {
overflow: auto;
margin: 10px;
padding-top: 10px;
}
.flow-tree .vue-recycle-scroller__item-wrapper{
height: 100%;

View File

@ -1,24 +1,14 @@
<template>
<div id="DeviceTree" style="border-right: 1px solid #EBEEF5; padding: 0 20px">
<div v-if="showHeader" class="page-header">
<el-form :inline="true" size="mini">
<el-form-item style="visibility: hidden">
<el-input
v-model="searchSrt"
style="margin-right: 1rem; width: 12rem;"
size="mini"
placeholder="关键字"
prefix-icon="el-icon-search"
clearable
@input="search"
/>
</el-form-item>
<el-form-item label="显示编号">
<el-checkbox v-model="showCode" />
</el-form-item>
</el-form>
<div id="regionTree" style="border-right: 1px solid #EBEEF5; height: 100%">
<div style="padding: 0 20px 0 10px;">
<el-input size="small" v-model="searchStr" @input="searchChange" suffix-icon="el-icon-search" placeholder="请输入搜索内容" clearable>
<!-- <el-select v-model="searchType" slot="prepend" placeholder="搜索类型" style="width: 80px">-->
<!-- <el-option label="目录" :value="0"></el-option>-->
<!-- <el-option label="通道" :value="1"></el-option>-->
<!-- </el-select>-->
</el-input>
</div>
<div>
<div v-if="!searchStr">
<el-alert
v-if="showAlert && edit"
title="操作提示"
@ -26,6 +16,10 @@
type="info"
style="text-align: left"
/>
<div v-if="edit" style="float: right;margin-right: 24px;margin-top: 18px; font-size: 14px" >
显示编号 <el-checkbox v-model="showCode" />
</div>
<vue-easy-tree
ref="veTree"
class="flow-tree"
@ -75,6 +69,49 @@
</template>
</vue-easy-tree>
</div>
<div v-if="searchStr" style="color: #606266; height: calc(100% - 32px); overflow: auto !important;">
<ul v-if="regionList.length > 0" style="list-style: none; margin: 0; padding: 10px">
<li v-for="item in regionList" :key="item.id" class="channel-list-li" style="height: 26px; align-items: center;cursor: pointer;" @click="listClickHandler(item)">
<span
v-if="chooseId !== item.deviceId"
style="color: #409EFF; font-size: 20px"
class="iconfont icon-bianzubeifen3"
/>
<span
v-if="chooseId === item.deviceId"
style="color: #c60135; font-size: 20px"
class="iconfont icon-bianzubeifen3"
/>
<div>
<div style="margin-left: 4px; margin-bottom: 3px; font-size: 15px">{{item.name}}</div>
<div style="margin-left: 4px; font-size: 13px; color: #808181">{{item.deviceId}}</div>
</div>
</li>
</ul>
<ul v-if="channelList.length > 0" style="list-style: none; margin: 0; padding: 10px; overflow: auto">
<li v-for="item in channelList" :key="item.id" class="channel-list-li" @click="channelLstClickHandler(item)">
<span
v-if="item.gbStatus === 'ON'"
style="color: #409EFF; font-size: 20px"
class="iconfont icon-shexiangtou2"
/>
<span
v-if="item.gbStatus !== 'ON'"
style="color: #808181; font-size: 20px"
class="iconfont icon-shexiangtou2"
/>
<div>
<div style="margin-left: 4px; margin-bottom: 3px; font-size: 15px">{{item.gbName}}</div>
<div style="margin-left: 4px; font-size: 13px; color: #808181">{{item.gbDeviceId}}</div>
</div>
</li>
</ul>
<div v-if="this.currentPage * this.count < this.total" style="text-align: center;">
<el-button type="text" @click="loadListMore">加载更多</el-button>
</div>
</div>
<regionEdit ref="regionEdit" />
<gbDeviceSelect ref="gbDeviceSelect" />
<GbChannelSelect ref="gbChannelSelect" data-type="civilCode" />
@ -86,6 +123,7 @@ import VueEasyTree from '@wchbrad/vue-easy-tree'
import regionEdit from './../dialog/regionEdit'
import gbDeviceSelect from './../dialog/GbDeviceSelect'
import GbChannelSelect from '../dialog/GbChannelSelect.vue'
import chooseCivilCode from '@/views/dialog/chooseCivilCode.vue'
export default {
name: 'DeviceTree',
@ -93,17 +131,24 @@ export default {
GbChannelSelect,
VueEasyTree, regionEdit, gbDeviceSelect
},
props: ['edit', 'enableAddChannel', 'clickEvent', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToCivilCode', 'treeHeight'],
props: ['edit', 'enableAddChannel', 'onChannelChange', 'showHeader', 'hasChannel', 'addChannelToCivilCode', 'treeHeight'],
data() {
return {
props: {
label: 'name'
label: 'name',
children: 'children'
},
searchType: 0,
showCode: false,
showAlert: true,
searchSrt: '',
searchStr: '',
chooseId: '',
treeData: []
treeData: [],
currentPage: this.defaultPage | 1,
count: this.defaultCount | 15,
total: 0,
regionList: [],
channelList: []
}
},
created() {
@ -117,8 +162,44 @@ export default {
// this.performance = "";
},
methods: {
search() {
searchChange() {
this.currentPage = 1
this.total = 0
if (this.edit) {
this.regionList = []
this.queryRegion()
}else {
this.channelList = []
this.queryChannelList()
}
},
loadListMore: function() {
this.currentPage += 1
if (this.edit) {
this.queryRegion()
}else {
this.queryChannelList()
}
},
queryRegion: function() {
this.$store.dispatch('region/queryTree', {
query: this.searchStr,
page: this.currentPage,
count: this.count
}).then(data => {
this.total = data.total
this.regionList = this.regionList.concat(data.list)
})
},
queryChannelList: function() {
this.$store.dispatch('commonChanel/getList', {
page: this.currentPage,
count: this.count,
query: this.searchStr
}).then(data => {
this.total = data.total
this.channelList = this.channelList.concat(data.list)
})
},
loadNode: function(node, resolve) {
if (node.level === 0) {
@ -135,7 +216,7 @@ export default {
return
}
this.$store.dispatch('region/getTreeList', {
query: this.searchSrt,
query: this.searchStr,
parent: node.data.id,
hasChannel: this.hasChannel
})
@ -158,7 +239,6 @@ export default {
if (!this.edit) {
return
}
console.log(node.level)
if (node.data.type === 0) {
const menuItem = [
{
@ -349,9 +429,17 @@ export default {
},
nodeClickHandler: function(data, node, tree) {
this.chooseId = data.deviceId
if (this.clickEvent) {
this.clickEvent(data)
}
this.$emit('clickEvent', data)
},
listClickHandler: function(data) {
this.chooseId = data.deviceId
this.$emit('clickEvent', data)
},
channelLstClickHandler: function(data) {
this.$emit('clickEvent', {
leaf: true,
id: data.gbId
})
}
}
}
@ -382,11 +470,18 @@ export default {
.flow-tree {
overflow: auto;
margin: 10px;
padding-top: 10px;
}
.flow-tree .vue-recycle-scroller__item-wrapper{
height: 100%;
overflow-x: auto;
}
.channel-list-li {
height: 24px;
align-items: center;
cursor: pointer;
display: grid;
grid-template-columns: 26px 1fr;
margin-bottom: 18px
}
</style>

View File

@ -1,35 +1,106 @@
<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">
<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 type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer"
v-if="Object.keys(this.player).length > 1">
<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"
:videoUrl="videoUrl" :error="videoError" :message="videoError"
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
<jessibucaPlayer
v-if="activePlayer === 'jessibuca'"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
: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"
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
:hasAudio="hasAudio" fluent autoplay live></rtc-player>
<rtc-player
v-if="activePlayer === 'webRTC'"
ref="webRTC"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
: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"
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
:hasAudio="hasAudio" fluent autoplay live></h265web>
<h265web
v-if="activePlayer === 'h265web'"
ref="h265web"
:video-url="videoUrl"
: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" :videoUrl="videoUrl" :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" :videoUrl="videoUrl" :error="videoError" :message="videoError"
height="100px" :hasAudio="hasAudio" fluent autoplay live></rtc-player>
<h265web v-if="Object.keys(this.player).length == 1 && this.player.h265web" ref="jessibuca"
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
height="100px" :hasAudio="hasAudio" fluent autoplay live></h265web>
<jessibucaPlayer
v-if="Object.keys(this.player).length == 1 && this.player.jessibuca"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
fluent
autoplay
live
/>
<rtc-player
v-if="Object.keys(this.player).length == 1 && this.player.webRTC"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
: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="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
:has-audio="hasAudio"
fluent
autoplay
live
/>
</div>
<div id="shared" style="text-align: right; margin-top: 1rem;">
@ -39,8 +110,12 @@
<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>
<i
class="cpoy-btn el-icon-document-copy"
title="点击拷贝"
style="cursor: pointer"
@click="copyUrl(getPlayerShared.sharedUrl)"
/>
</template>
</el-input>
</div>
@ -48,20 +123,28 @@
<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>
<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="点击拷贝"
v-clipboard="getPlayerShared.sharedRtmp"
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
<el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
<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"></i>
更多地址<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu>
<el-dropdown-item v-if="streamInfo.flv" :command="streamInfo.flv">
@ -160,100 +243,106 @@
</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">
<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"></i>
<div class="control-inner-btn control-inner"></div>
<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"></i>
<div class="control-inner-btn control-inner"></div>
<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"></i>
<div class="control-inner-btn control-inner"></div>
<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"></i>
<div class="control-inner-btn control-inner"></div>
<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"></i></div>
<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"></el-slider>
<el-slider v-model="controSpeed" :max="100" />
</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 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="" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')" title="变倍-">
<i class="el-icon-zoom-out control-zoom-btn" style="font-size: 1.5rem;"></i>
<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 @mousedown="focusCamera('near')" @mouseup="focusCamera('stop')" title="聚焦+">
<i class="iconfont icon-bianjiao-fangda control-zoom-btn" style="font-size: 1.5rem;"></i>
<div title="聚焦+" @mousedown="focusCamera('near')" @mouseup="focusCamera('stop')">
<i class="iconfont icon-bianjiao-fangda control-zoom-btn" style="font-size: 1.5rem;" />
</div>
<div @mousedown="focusCamera('far')" @mouseup="focusCamera('stop')" title="聚焦-">
<i class="iconfont icon-bianjiao-suoxiao control-zoom-btn" style="font-size: 1.5rem;"></i>
<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 @mousedown="irisCamera('in')" @mouseup="irisCamera('stop')" title="光圈+">
<i class="iconfont icon-guangquan control-zoom-btn" style="font-size: 1.5rem;"></i>
<div title="光圈+" @mousedown="irisCamera('in')" @mouseup="irisCamera('stop')">
<i class="iconfont icon-guangquan control-zoom-btn" style="font-size: 1.5rem;" />
</div>
<div @mousedown="pirisCamera('out')" @mouseup="irisCamera('stop')" title="光圈-">
<i class="iconfont icon-guangquan- control-zoom-btn" style="font-size: 1.5rem;"></i>
<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" >
<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>
<el-option label="巡航组" value="cruise"></el-option>
<el-option label="自动扫描" value="scan"></el-option>
<el-option label="雨刷" value="wiper"></el-option>
<el-option label="辅助开关" value="switch"></el-option>
<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 :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'preset'" style="margin-top: 1rem"></ptzPreset>
<ptzCruising :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'cruise'" style="margin-top: 1rem"></ptzCruising>
<ptzScan :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'scan'" style="margin-top: 1rem"></ptzScan>
<ptzWiper :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'wiper'" style="margin-top: 1rem"></ptzWiper>
<ptzSwitch :channelDeviceId="channelId" :deviceId="deviceId" v-if="ptzMethod === 'switch'" style="margin-top: 1rem"></ptzSwitch>
<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" :mediaServerId="mediaServerId"></mediaInfo>
<el-tab-pane label="编码信息" name="codec">
<mediaInfo ref="mediaInfo" :app="app" :stream="streamId" :media-server-id="mediaServerId" />
</el-tab-pane>
<el-tab-pane label="语音对讲" name="broadcast">
<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-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 :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 @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2"
circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/>
<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>
@ -271,62 +360,45 @@
</template>
<script>
import rtcPlayer from '../dialog/rtcPlayer.vue'
import LivePlayer from '@liveqing/liveplayer'
import elDragDialog from '@/directive/el-drag-dialog'
import crypto from 'crypto'
import jessibucaPlayer from '../common/jessibuca.vue'
import PtzPreset from "../common/ptzPreset.vue";
import PtzCruising from "../common/ptzCruising.vue";
import ptzScan from "../common/ptzScan.vue";
import ptzWiper from "../common/ptzWiper.vue";
import ptzSwitch from "../common/ptzSwitch.vue";
import mediaInfo from "../common/mediaInfo.vue";
import H265web from "../common/h265web.vue";
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',
props: {},
name: 'DevicePlayer',
directives: { elDragDialog },
components: {
H265web,
PtzPreset,PtzCruising,ptzScan,ptzWiper,ptzSwitch,mediaInfo,
LivePlayer, jessibucaPlayer, rtcPlayer,
},
computed: {
getPlayerShared: function () {
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></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]
}
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",
activePlayer: 'jessibuca',
//
player: {
jessibuca: ["ws_flv", "wss_flv"],
webRTC: ["rtc", "rtcs"],
h265web: ["ws_flv", "wss_flv"],
jessibuca: ['ws_flv', 'wss_flv'],
webRTC: ['rtc', 'rtcs'],
h265web: ['ws_flv', 'wss_flv']
},
showVideoDialog: false,
channelId: null,
streamId: '',
ptzMethod: 'preset',
ptzPresetId: '',
app: '',
mediaServerId: '',
deviceId: '',
channelId: '',
tabActiveName: 'media',
hasAudio: false,
loadingRecords: false,
@ -344,164 +416,159 @@ export default {
scanGroup: 0,
tracks: [],
showPtz: true,
showBroadcast: true,
showRrecord: true,
sliderTime: 0,
seekTime: 0,
recordStartTime: 0,
showTimeText: "00:00:00",
showTimeText: '00:00:00',
streamInfo: null,
broadcastMode: true,
broadcastRtc: null,
broadcastStatus: -1, // -2 -1 0 1
};
broadcastStatus: -1 // -2 -1 0 1
}
},
computed: {
getPlayerShared: function() {
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '' + window.location.origin + '<iframe src="/public#/play/wasm/"></iframe>' + encodeURIComponent(this.videoUrl) + '',
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) {
tabHandleClick: function(tab, event) {
console.log(tab)
this.tracks = [];
if (tab.name === "codec") {
this.tracks = []
if (tab.name === 'codec') {
this.$refs.mediaInfo.startTask()
}else {
} else {
this.$refs.mediaInfo.stopTask()
}
},
changePlayer: function (tab) {
changePlayer: function(tab) {
console.log(this.player[tab.name][0])
this.activePlayer = tab.name;
this.activePlayer = tab.name
this.videoUrl = this.getUrlByStreamInfo()
console.log(this.videoUrl)
},
openDialog: function (tab, deviceId, channelId, param) {
openDialog: function(tab, channelId, param) {
if (this.showVideoDialog) {
return;
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();
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":
case 'media':
this.play(param.streamInfo, param.hasAudio)
break;
case "streamPlay":
this.tabActiveName = "media";
this.showRrecord = false;
this.showPtz = false;
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;
break
case 'control':
break
}
},
play: function (streamInfo, hasAudio) {
this.streamInfo = streamInfo;
this.hasAudio = hasAudio;
this.isLoging = false;
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.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;
streamInfo = this.streamInfo.transcodeStream
}
if (location.protocol === "https:") {
if (location.protocol === 'https:') {
this.videoUrl = streamInfo[this.player[this.activePlayer][1]]
} else {
this.videoUrl = streamInfo[this.player[this.activePlayer][0]]
}
return this.videoUrl;
return this.videoUrl
},
playFromStreamInfo: function (realHasAudio, streamInfo) {
this.showVideoDialog = true;
this.hasaudio = realHasAudio && this.hasaudio;
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 {
} 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()
close: function() {
console.log('关闭视频')
if (this.$refs[this.activePlayer]) {
this.$refs[this.activePlayer].pause()
}
this.videoUrl = ''
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.$store.dispatch('commonChanel/ptz',
[
this.channelId,
command,
parseInt(this.controSpeed * 255 / 100),
parseInt(this.controSpeed * 255 / 100),
parseInt(this.controSpeed * 16 / 100)
])
},
ptzCamera: function (command) {
console.log('云台控制:' + command);
let that = this;
this.$axios({
method: 'get',
url: '/api/front-end/ptz/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + parseInt(this.controSpeed * 255/100) + '&verticalSpeed=' + parseInt(this.controSpeed * 255/100) + '&zoomSpeed=' + parseInt(this.controSpeed * 16/100)
}).then(function (res) {
});
irisCamera: function(command) {
this.$store.dispatch('commonChanel/iris',
[
this.channelId,
command,
parseInt(this.controSpeed * 255 / 100)
])
},
irisCamera: function (command) {
this.$axios({
method: 'get',
url: '/api/front-end/fi/iris/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&speed=' + parseInt(this.controSpeed * 255/100)
}).then(function (res) {
});
focusCamera: function(command) {
this.$store.dispatch('commonChanel/focus',
[
this.channelId,
command,
parseInt(this.controSpeed * 255 / 100)
])
},
focusCamera: function (command) {
this.$axios({
method: 'get',
url: '/api/front-end/fi/focus/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&speed=' + parseInt(this.controSpeed * 255/100)
}).then(function (res) {
});
// //////////////////////////////////////////////
videoError: function(e) {
console.log('播放器错误:' + JSON.stringify(e))
},
////////////////////////////////////////////////
videoError: function (e) {
console.log("播放器错误:" + JSON.stringify(e));
},
copyUrl: function (dropdownItem) {
copyUrl: function(dropdownItem) {
console.log(dropdownItem)
this.$copyText(dropdownItem).then((e) => {
this.$message.success({
showClose: true,
message: "成功拷贝到粘贴板"
message: '成功拷贝到粘贴板'
})
}, (e) => {
@ -509,157 +576,123 @@ export default {
},
getBroadcastStatus() {
if (this.broadcastStatus == -2) {
return "primary"
return 'primary'
}
if (this.broadcastStatus == -1) {
return "primary"
return 'primary'
}
if (this.broadcastStatus == 0) {
return "warning"
return 'warning'
}
if (this.broadcastStatus == 1) {
return "danger"
if (this.broadcastStatus === 1) {
return 'danger'
}
},
broadcastStatusClick() {
if (this.broadcastStatus == -1) {
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.$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)
}
} else {
this.$message({
showClose: true,
message: res.data.msg,
type: "error",
});
}
});
})
} else if (this.broadcastStatus === 1) {
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;
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)
url += '&sign=' + crypto.createHash('md5').update(pushKey, 'utf8').digest('hex')
console.log('开始语音喊话: ' + url)
this.broadcastRtc = new ZLMRTCClient.Endpoint({
debug: true, //
zlmsdpUrl: url, //
zlmsdpUrl: url, //
simulecast: false,
useCamera: false,
audioEnable: true,
videoEnable: false,
recvOnly: false,
recvOnly: false
})
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {//
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.broadcastStatus = -1
})
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE
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.broadcastStatus = -1
})
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {// offer anwser
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
})
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;
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
})
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.broadcastStatus = -1
})
}).catch(e => {
this.$message({
showClose: true,
message: res.data.msg,
type: "error",
});
}
});
message: e,
type: 'error'
})
this.broadcastStatus = -1
})
},
stopBroadcast() {
this.broadcastRtc.close()
this.broadcastStatus = -1
this.$store.dispatch('play/broadcastStop', [this.channelId])
}
}
};
}
</script>
<style>

View 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" placeholder="请选择预置点">
<el-option
v-for="item in allPresetList"
:key="item.presetId"
:label="item.presetName"
: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 {
cruiseId: 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',
[this.channelId, 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('commonChanel/deletePointForCruise',
[this.channelId, 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('commonChanel/deletePointForCruise',
[this.channelId, 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('commonChanel/setCruiseSpeed',
[this.channelId, 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('commonChanel/setCruiseTime',
[this.channelId, 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('commonChanel/startCruise',
[this.channelId, 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('commonChanel/stopCruise',
[this.channelId, 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>

View 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: ['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', [this.channelId, 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', [this.channelId, 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', [this.channelId, preset.presetId])
.then(data => {
setTimeout(() => {
this.getPresetList()
}, 1000)
}).catch((error) => {
this.$message({
showClose: true,
message: error,
type: 'error'
})
}).finally(() => {
loading.close()
})
}).catch(() => {
})
}
}
}
</script>

View 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: ['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', [this.channelId, 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('commonChanel/setLeftForScan', [this.channelId, 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', [this.channelId, 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', [this.channelId, 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', [this.channelId, 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>

View File

@ -1,17 +1,16 @@
<template>
<div id="ptzScan">
<el-form size="mini" :inline="true" >
<el-form-item >
<el-form size="mini" :inline="true">
<el-form-item>
<el-input
v-model="switchId"
min="1"
max="4095"
placeholder="开关编号"
addonBefore="开关编号"
addonAfter="(2-255)"
v-model="switchId"
addon-before="开关编号"
addon-after="(2-255)"
size="mini"
>
</el-input>
/>
</el-form-item>
<el-form-item>
<el-button size="mini" @click="open('on')">开启</el-button>
@ -25,18 +24,18 @@
<script>
export default {
name: "ptzScan",
props: [ 'channelDeviceId', 'deviceId'],
name: 'PtzScan',
components: {},
created() {
},
props: ['channelId'],
data() {
return {
switchId: 1,
};
switchId: 1
}
},
created() {
},
methods: {
open: function (command){
open: function(command) {
const loading = this.$loading({
lock: true,
fullscreen: true,
@ -44,39 +43,26 @@ export default {
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
this.$axios({
method: 'get',
url: `/api/front-end/auxiliary/${this.deviceId}/${this.channelDeviceId}`,
params: {
command: command,
switchId: this.switchId,
}
}).then((res)=> {
if (res.data.code === 0) {
this.$store.dispatch('commonChanel/auxiliary', [this.channelId, command, this.switchId])
.then(data => {
this.$message({
showClose: true,
message: "保存成功",
message: '保存成功',
type: 'success'
});
}else {
})
})
.catch((error) => {
this.$message({
showClose: true,
message: res.data.msg,
message: error,
type: 'error'
});
}
}).catch((error)=> {
this.$message({
showClose: true,
message: error,
type: 'error'
});
}).finally(()=>{
loading.close()
})
},
},
};
})
}).finally(() => {
loading.close()
})
}
}
}
</script>
<style>
.channel-form {

View File

@ -8,16 +8,16 @@
<script>
export default {
name: "ptzWiper",
props: [ 'channelDeviceId', 'deviceId'],
name: 'PtzWiper',
components: {},
props: ['channelId'],
data() {
return {}
},
created() {
},
data() {
return {};
},
methods: {
open: function (command){
open: function(command) {
const loading = this.$loading({
lock: true,
fullscreen: true,
@ -25,38 +25,26 @@ export default {
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
this.$axios({
method: 'get',
url: `/api/front-end/wiper/${this.deviceId}/${this.channelDeviceId}`,
params: {
command: command,
}
}).then((res)=> {
if (res.data.code === 0) {
this.$store.dispatch('commonChanel/wiper', [this.channelId, command])
.then(data => {
this.$message({
showClose: true,
message: "保存成功",
message: '保存成功',
type: 'success'
});
}else {
})
})
.catch((error) => {
this.$message({
showClose: true,
message: res.data.msg,
message: error,
type: 'error'
});
}
}).catch((error)=> {
this.$message({
showClose: true,
message: error,
type: 'error'
});
}).finally(()=>{
loading.close()
})
},
},
};
})
}).finally(() => {
loading.close()
})
}
}
}
</script>
<style>
.channel-form {

View File

@ -1,17 +1,21 @@
<template>
<div id="ConsoleNodeLoad" style="width: 100%; height: 100%; background: #FFFFFF; text-align: center">
<ve-histogram ref="consoleNodeLoad" :data="chartData" :extend="extend" :settings="chartSettings" width="100%" height="100%" :legend-visible="true" />
<ve-histogram ref="consoleNodeLoad" :data="chartData" :extend="extend" :events="events" :settings="chartSettings"
width="100%" height="100%" :legend-visible="true"/>
<HasStreamChannel ref="hasStreamChannel"/>
</div>
</template>
<script>
import veHistogram from 'v-charts/lib/histogram'
import HasStreamChannel from "@/views/dialog/hasStreamChannel";
export default {
name: 'ConsoleNodeLoad',
components: {
veHistogram
veHistogram,
HasStreamChannel
},
data() {
return {
@ -43,6 +47,9 @@ export default {
show: true,
position: 'top'
}
},
events: {
click: this.onClick
}
}
},
@ -56,10 +63,14 @@ export default {
destroyed() {
},
methods: {
setData: function(data) {
setData: function (data) {
this.chartData.rows = data
},
onClick(v) {
if (v.seriesName === "国标收流") {
this.$refs.hasStreamChannel.openDialog();
}
}
}
}
</script>

View File

@ -7,7 +7,7 @@
</el-form-item>
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
placeholder="关键字"
prefix-icon="el-icon-search"
@ -248,7 +248,7 @@ export default {
videoComponentList: [],
currentPlayerInfo: {}, //
updateLooper: 0, //
searchSrt: '',
searchStr: '',
channelType: '',
online: '',
subStream: '',
@ -322,7 +322,7 @@ export default {
this.$store.dispatch('device/queryChannels', [this.deviceId, {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
online: this.online,
channelType: this.channelType
}]).then(data => {
@ -465,7 +465,7 @@ export default {
var url = `/${this.$router.currentRoute.name}/${this.$router.currentRoute.params.deviceId}/${itemData.deviceId}`
this.$router.push(url).then(() => {
this.searchSrt = ''
this.searchStr = ''
this.channelType = ''
this.online = ''
this.initParam()
@ -477,7 +477,7 @@ export default {
{
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
online: this.online,
channelType: this.channelType
},

View File

@ -3,7 +3,7 @@
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
placeholder="关键字"
prefix-icon="el-icon-search"
@ -185,7 +185,7 @@ export default {
return {
deviceList: [], //
currentDevice: {}, //
searchSrt: '',
searchStr: '',
online: null,
videoComponentList: [],
updateLooper: 0, //
@ -231,7 +231,7 @@ export default {
this.$store.dispatch('device/queryDevices', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
status: this.online
}).then((data) => {
this.total = data.total

View File

@ -14,7 +14,7 @@
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
placeholder="关键字"
prefix-icon="el-icon-search"
@ -117,7 +117,7 @@ export default {
showDialog: false,
channelList: [], //
currentDevice: {}, //
searchSrt: '',
searchStr: '',
online: null,
channelType: '',
videoComponentList: [],
@ -154,7 +154,7 @@ export default {
page: this.currentPage,
count: this.count,
channelType: this.channelType,
query: this.searchSrt,
query: this.searchStr,
online: this.online
})
.then(data => {
@ -167,7 +167,7 @@ export default {
this.$store.dispatch('commonChanel/getParentList', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
channelType: this.channelType,
online: this.online
})

View File

@ -13,7 +13,7 @@
><el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
size="mini"
placeholder="关键字"
@ -92,7 +92,7 @@ export default {
showDialog: false,
deviceList: [], //
currentDevice: {}, //
searchSrt: '',
searchStr: '',
online: null,
videoComponentList: [],
updateLooper: 0, //
@ -129,7 +129,7 @@ export default {
this.$store.dispatch('device/queryDevices', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
status: this.online
})
.then(data => {

View File

@ -14,7 +14,7 @@
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
size="mini"
placeholder="关键字"
@ -132,7 +132,7 @@ export default {
return {
showDialog: false,
channelList: [], //
searchSrt: '',
searchStr: '',
online: null,
channelType: '',
winHeight: 580,
@ -165,7 +165,7 @@ export default {
page: this.currentPage,
count: this.count,
channelType: this.channelType,
query: this.searchSrt,
query: this.searchStr,
online: this.online
})
.then(data => {

View File

@ -14,7 +14,7 @@
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
size="mini"
placeholder="关键字"
@ -144,7 +144,7 @@ export default {
return {
showDialog: false,
channelList: [], //
searchSrt: '',
searchStr: '',
online: null,
channelType: '',
winHeight: 580,
@ -177,7 +177,7 @@ export default {
page: this.currentPage,
count: this.count,
channelType: this.channelType,
query: this.searchSrt,
query: this.searchStr,
online: this.online
})
.then((data) => {

View File

@ -6,6 +6,7 @@
v-el-drag-dialog
title="视频播放"
top="0"
append-to-body
:close-on-click-modal="false"
:visible.sync="showVideoDialog"
@close="close()"

View File

@ -0,0 +1,147 @@
<template>
<div>
<el-dialog
v-if="showDialog"
v-el-drag-dialog
:visible.sync="showDialog"
title="国标收流列表"
width="70%"
top="5rem"
append-to-body
:close-on-click-modal="false"
>
<el-form :inline="true" size="mini" @submit.native.prevent>
<el-form-item label="搜索">
<el-input
v-model="query"
placeholder="关键字"
prefix-icon="el-icon-search"
clearable
@input="getChannelList"
/>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="channelList" :height="500" stripe>
<el-table-column prop="parentDeviceId" label="设备编号" min-width="180"/>
<el-table-column prop="parentName" label="设备名称" min-width="180"/>
<el-table-column prop="deviceId" label="通道编号" min-width="180"/>
<el-table-column prop="name" label="通道名称" min-width="180"/>
<el-table-column prop="ptzType" label="云台类型" min-width="100">
<template v-slot:default="scope">
<div>{{ scope.row.ptzTypeText }}</div>
</template>
</el-table-column>
<el-table-column label="操作" min-width="120" fixed="right">
<template v-slot:default="scope">
<el-button
size="medium"
icon="el-icon-video-play"
type="text"
:loading="scope.row.playing"
@click="sendDevicePush(scope.row)"
>播放
</el-button>
<el-button
size="medium"
icon="el-icon-switch-button"
type="text"
style="color: #f56c6c"
@click="stopDevicePush(scope.row)"
>停止
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin-top: 10px; text-align: right"
:current-page="currentPage"
:page-size="count"
:page-sizes="[15, 25, 35, 50]"
layout="total, sizes, prev, pager, next"
:total="total"
@size-change="handleSizeChange"
@current-change="currentChange"
/>
</el-dialog>
<devicePlayer ref="devicePlayer"/>
</div>
</template>
<script>
import devicePlayer from "@/views/dialog/devicePlayer";
import elDragDialog from "@/directive/el-drag-dialog";
export default {
name: "HasStreamChannel",
directives: {elDragDialog},
components: {devicePlayer},
data() {
return {
showDialog: false,
loading: false,
playing: false,
channelList: [],
query: null,
currentPage: 1,
count: 15,
total: 0
}
},
methods: {
openDialog: function () {
this.showDialog = true;
this.getChannelList();
},
getChannelList: function () {
this.loading = true;
this.$store.dispatch("device/queryHasStreamChannels", {
page: this.currentPage,
count: this.count,
query: this.query
}).then((data) => {
this.total = data.total
this.channelList = data.list
}).finally(() => {
this.loading = false
})
},
currentChange: function (val) {
this.currentPage = val
this.getChannelList()
},
handleSizeChange: function (val) {
this.count = val
this.getChannelList()
},
sendDevicePush: function (row) {
const deviceId = row.parentDeviceId
const channelId = row.deviceId
this.$set(row, "playing", true)
this.$store.dispatch("play/play", [deviceId, channelId])
.then((data) => {
this.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
streamInfo: data,
hasAudio: row.hasAudio
})
})
.finally(() => {
this.$set(row, "playing", false)
})
},
stopDevicePush: function (row) {
this.$store.dispatch("play/stop", [row.parentDeviceId, row.deviceId]).then(_ => {
this.getChannelList();
}).catch((error) => {
if (error.response.status === 402) {
this.getChannelList();
} else {
this.$message.error({showClose: true, message: error})
}
})
}
}
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<div id="linkChannelRecord" style="width: 100%; background-color: #FFFFFF; display: grid; grid-template-columns: 200px auto;">
<div id="linkChannelRecord" style="width: 100%; background-color: #FFFFFF;">
<el-dialog v-el-drag-dialog v-if="showDialog" v-loading="dialogLoading" title="通道关联" top="2rem" width="80%" :close-on-click-modal="false" :visible.sync="showDialog" :destroy-on-close="true" @close="close()">
<div style="display: grid; grid-template-columns: 100px auto;">
<div style="display: grid; grid-template-columns: 100px minmax(0, 1fr);">
<el-tabs v-model="hasLink" tab-position="left" style="" @tab-click="search">
<el-tab-pane label="未关联" name="false" />
<el-tab-pane label="已关联" name="true" />
@ -10,7 +10,7 @@
<el-form :inline="true" size="mini">
<el-form-item label="搜索">
<el-input
v-model="searchSrt"
v-model="searchStr"
style="margin-right: 1rem; width: auto;"
placeholder="关键字"
prefix-icon="el-icon-search"
@ -120,7 +120,7 @@ export default {
showDialog: false,
chooseData: {},
channelList: [],
searchSrt: '',
searchStr: '',
channelType: '',
online: '',
hasLink: 'false',
@ -161,7 +161,7 @@ export default {
this.$store.dispatch('recordPlan/queryChannelList', {
page: this.currentPage,
count: this.count,
query: this.searchSrt,
query: this.searchStr,
online: this.online,
channelType: this.channelType,
planId: this.planId,

Some files were not shown because too many files have changed in this diff Show More