新增实时监控功能
左边是设备通道树,右边是分屏预览
This commit is contained in:
parent
8cbbf1470b
commit
3789e8616f
11
pom.xml
11
pom.xml
@ -277,5 +277,16 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/java</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.xml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
|||||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
|
||||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||||
|
import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
|
||||||
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
|
|
||||||
@ -93,6 +94,13 @@ public interface IVideoManagerStorager {
|
|||||||
|
|
||||||
public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
|
public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某个设备的通道树
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<DeviceChannelTree> tree(String deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取某个设备的通道列表
|
* 获取某个设备的通道列表
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.genersoft.iot.vmp.storager.dao;
|
package com.genersoft.iot.vmp.storager.dao;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
|
import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
|
||||||
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
||||||
import org.apache.ibatis.annotations.*;
|
import org.apache.ibatis.annotations.*;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@ -201,4 +202,6 @@ public interface DeviceChannelMapper {
|
|||||||
|
|
||||||
@Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND status=1")
|
@Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND status=1")
|
||||||
List<DeviceChannel> queryOnlineChannelsByDeviceId(String deviceId);
|
List<DeviceChannel> queryOnlineChannelsByDeviceId(String deviceId);
|
||||||
|
|
||||||
|
List<DeviceChannelTree> tree(String deviceId);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper">
|
||||||
|
|
||||||
|
<!-- 通用查询映射结果 -->
|
||||||
|
<resultMap id="treeNodeResultMap" type="com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTreeNode">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="parentId" property="parentId"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="title" property="title"/>
|
||||||
|
<result column="value" property="value"/>
|
||||||
|
<result column="key" property="key"/>
|
||||||
|
<result column="deviceId" property="deviceId"/>
|
||||||
|
<result column="channelId" property="channelId"/>
|
||||||
|
<result column="longitude" property="lng"/>
|
||||||
|
<result column="latitude" property="lat"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
|
||||||
|
<select id="tree" resultMap="treeNodeResultMap">
|
||||||
|
SELECT
|
||||||
|
channelId,
|
||||||
|
channelId as id,
|
||||||
|
deviceId,
|
||||||
|
parentId,
|
||||||
|
status,
|
||||||
|
name as title,
|
||||||
|
channelId as "value",
|
||||||
|
channelId as "key",
|
||||||
|
channelId,
|
||||||
|
longitude,
|
||||||
|
latitude
|
||||||
|
from device_channel
|
||||||
|
where deviceId = #{deviceId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
@ -13,6 +13,8 @@ import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
|||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
||||||
import com.genersoft.iot.vmp.storager.dao.*;
|
import com.genersoft.iot.vmp.storager.dao.*;
|
||||||
|
import com.genersoft.iot.vmp.utils.node.ForestNodeMerger;
|
||||||
|
import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
|
||||||
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
@ -328,6 +330,11 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
|
|||||||
return deviceChannelMapper.queryChannelsByDeviceIdWithStartAndLimit(deviceId, null, query, hasSubChannel, online, start, limit);
|
return deviceChannelMapper.queryChannelsByDeviceIdWithStartAndLimit(deviceId, null, query, hasSubChannel, online, start, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceChannelTree> tree(String deviceId) {
|
||||||
|
return ForestNodeMerger.merge(deviceChannelMapper.tree(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
|
public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
|
||||||
return deviceChannelMapper.queryChannels(deviceId, null,null, null, null);
|
return deviceChannelMapper.queryChannels(deviceId, null,null, null, null);
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class CollectionUtil {
|
||||||
|
|
||||||
|
public static <T> boolean contains(T[] array, final T element) {
|
||||||
|
return array != null && Arrays.stream(array).anyMatch((x) -> {
|
||||||
|
return ObjectUtils.nullSafeEquals(x, element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
41
src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java
Normal file
41
src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ObjectUtils {
|
||||||
|
public static boolean nullSafeEquals(Object o1, Object o2) {
|
||||||
|
if (o1 == o2) {
|
||||||
|
return true;
|
||||||
|
} else if (o1 != null && o2 != null) {
|
||||||
|
if (o1.equals(o2)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return o1.getClass().isArray() && o2.getClass().isArray() && arrayEquals(o1, o2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean arrayEquals(Object o1, Object o2) {
|
||||||
|
if (o1 instanceof Object[] && o2 instanceof Object[]) {
|
||||||
|
return Arrays.equals((Object[])((Object[])o1), (Object[])((Object[])o2));
|
||||||
|
} else if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
|
||||||
|
return Arrays.equals((boolean[])((boolean[])o1), (boolean[])((boolean[])o2));
|
||||||
|
} else if (o1 instanceof byte[] && o2 instanceof byte[]) {
|
||||||
|
return Arrays.equals((byte[])((byte[])o1), (byte[])((byte[])o2));
|
||||||
|
} else if (o1 instanceof char[] && o2 instanceof char[]) {
|
||||||
|
return Arrays.equals((char[])((char[])o1), (char[])((char[])o2));
|
||||||
|
} else if (o1 instanceof double[] && o2 instanceof double[]) {
|
||||||
|
return Arrays.equals((double[])((double[])o1), (double[])((double[])o2));
|
||||||
|
} else if (o1 instanceof float[] && o2 instanceof float[]) {
|
||||||
|
return Arrays.equals((float[])((float[])o1), (float[])((float[])o2));
|
||||||
|
} else if (o1 instanceof int[] && o2 instanceof int[]) {
|
||||||
|
return Arrays.equals((int[])((int[])o1), (int[])((int[])o2));
|
||||||
|
} else if (o1 instanceof long[] && o2 instanceof long[]) {
|
||||||
|
return Arrays.equals((long[])((long[])o1), (long[])((long[])o2));
|
||||||
|
} else {
|
||||||
|
return o1 instanceof short[] && o2 instanceof short[] && Arrays.equals((short[]) ((short[]) o1), (short[]) ((short[]) o2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java
Normal file
54
src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点基类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BaseNode<T> implements INode<T> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父节点ID
|
||||||
|
*/
|
||||||
|
protected String parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子孙节点
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
protected List<T> children = new ArrayList<T>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有子孙节点
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private Boolean hasChildren;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有子孙节点
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean getHasChildren() {
|
||||||
|
if (children.size() > 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return this.hasChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 森林节点类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class ForestNode extends BaseNode<ForestNode> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点内容
|
||||||
|
*/
|
||||||
|
private Object content;
|
||||||
|
|
||||||
|
public ForestNode(String id, String parentId, Object content) {
|
||||||
|
this.id = id;
|
||||||
|
this.parentId = parentId;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 森林管理类
|
||||||
|
*
|
||||||
|
* @author smallchill
|
||||||
|
*/
|
||||||
|
public class ForestNodeManager<T extends INode<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 森林的所有节点
|
||||||
|
*/
|
||||||
|
private final ImmutableMap<String, T> nodeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 森林的父节点ID
|
||||||
|
*/
|
||||||
|
private final Map<String, Object> parentIdMap = Maps.newHashMap();
|
||||||
|
|
||||||
|
public ForestNodeManager(List<T> nodes) {
|
||||||
|
nodeMap = Maps.uniqueIndex(nodes, INode::getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据节点ID获取一个节点
|
||||||
|
*
|
||||||
|
* @param id 节点ID
|
||||||
|
* @return 对应的节点对象
|
||||||
|
*/
|
||||||
|
public INode<T> getTreeNodeAt(String id) {
|
||||||
|
if (nodeMap.containsKey(id)) {
|
||||||
|
return nodeMap.get(id);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加父节点ID
|
||||||
|
*
|
||||||
|
* @param parentId 父节点ID
|
||||||
|
*/
|
||||||
|
public void addParentId(String parentId) {
|
||||||
|
parentIdMap.put(parentId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取树的根节点(一个森林对应多颗树)
|
||||||
|
*
|
||||||
|
* @return 树的根节点集合
|
||||||
|
*/
|
||||||
|
public List<T> getRoot() {
|
||||||
|
List<T> roots = new ArrayList<>();
|
||||||
|
nodeMap.forEach((key, node) -> {
|
||||||
|
if (node.getParentId() == null || parentIdMap.containsKey(node.getId())) {
|
||||||
|
roots.add(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.utils.CollectionUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 森林节点归并类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ForestNodeMerger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将节点数组归并为一个森林(多棵树)(填充节点的children域)
|
||||||
|
* 时间复杂度为O(n^2)
|
||||||
|
*
|
||||||
|
* @param items 节点域
|
||||||
|
* @return 多棵树的根节点集合
|
||||||
|
*/
|
||||||
|
public static <T extends INode<T>> List<T> merge(List<T> items) {
|
||||||
|
ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items);
|
||||||
|
items.forEach(forestNode -> {
|
||||||
|
if (forestNode.getParentId() != null) {
|
||||||
|
INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
|
||||||
|
if (node != null) {
|
||||||
|
node.getChildren().add(forestNode);
|
||||||
|
} else {
|
||||||
|
forestNodeManager.addParentId(forestNode.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return forestNodeManager.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends INode<T>> List<T> merge(List<T> items, String[] parentIds) {
|
||||||
|
ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items);
|
||||||
|
items.forEach(forestNode -> {
|
||||||
|
if (forestNode.getParentId() != null) {
|
||||||
|
INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
|
||||||
|
if (CollectionUtil.contains(parentIds, forestNode.getId())){
|
||||||
|
forestNodeManager.addParentId(forestNode.getId());
|
||||||
|
} else {
|
||||||
|
if (node != null){
|
||||||
|
node.getChildren().add(forestNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return forestNodeManager.getRoot();
|
||||||
|
}
|
||||||
|
}
|
42
src/main/java/com/genersoft/iot/vmp/utils/node/INode.java
Normal file
42
src/main/java/com/genersoft/iot/vmp/utils/node/INode.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 节点
|
||||||
|
*/
|
||||||
|
public interface INode<T> extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父主键
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String getParentId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子孙节点
|
||||||
|
*
|
||||||
|
* @return List<T>
|
||||||
|
*/
|
||||||
|
List<T> getChildren();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有子孙节点
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
default Boolean getHasChildren() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java
Normal file
21
src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.genersoft.iot.vmp.utils.node;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树型节点类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class TreeNode extends BaseNode<TreeNode> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.genersoft.iot.vmp.vmanager.bean;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
|
import com.genersoft.iot.vmp.utils.node.INode;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ApiModel(value = "DeviceChannelTree对象", description = "DeviceChannelTree对象")
|
||||||
|
public class DeviceChannelTree extends DeviceChannel implements INode<DeviceChannelTree> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父节点ID
|
||||||
|
*/
|
||||||
|
private String parentId;
|
||||||
|
|
||||||
|
private String parentName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子孙节点
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private List<DeviceChannelTree> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有子孙节点
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
private Boolean hasChildren;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceChannelTree> getChildren() {
|
||||||
|
if (this.children == null) {
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.genersoft.iot.vmp.vmanager.bean;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.utils.node.TreeNode;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class DeviceChannelTreeNode extends TreeNode {
|
||||||
|
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
private String channelId;
|
||||||
|
|
||||||
|
private Double lng;
|
||||||
|
|
||||||
|
private Double lat;
|
||||||
|
}
|
@ -1,32 +1,35 @@
|
|||||||
package com.genersoft.iot.vmp.vmanager.bean;
|
package com.genersoft.iot.vmp.vmanager.bean;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
public class WVPResult<T> {
|
public class WVPResult<T> {
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
private String msg;
|
private String msg;
|
||||||
private T data;
|
private T data;
|
||||||
|
|
||||||
public int getCode() {
|
private static final Integer SUCCESS = 200;
|
||||||
return code;
|
private static final Integer FAILED = 400;
|
||||||
|
|
||||||
|
public static <T> WVPResult<T> Data(T t, String msg) {
|
||||||
|
return new WVPResult<>(SUCCESS, msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCode(int code) {
|
public static <T> WVPResult<T> Data(T t) {
|
||||||
this.code = code;
|
return Data(t, "成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMsg() {
|
public static <T> WVPResult<T> fail(int code, String msg) {
|
||||||
return msg;
|
return new WVPResult<>(code, msg, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMsg(String msg) {
|
public static <T> WVPResult<T> fail(String msg) {
|
||||||
this.msg = msg;
|
return fail(FAILED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(T data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
|||||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
||||||
|
import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
import io.swagger.annotations.ApiImplicitParams;
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
@ -25,6 +27,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.context.request.async.DeferredResult;
|
import org.springframework.web.context.request.async.DeferredResult;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Api(tags = "国标设备查询", value = "国标设备查询")
|
@Api(tags = "国标设备查询", value = "国标设备查询")
|
||||||
@ -431,5 +434,9 @@ public class DeviceQuery {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{deviceId}/tree")
|
||||||
|
@ApiOperation(value = "通道树形结构", notes = "通道树形结构")
|
||||||
|
public WVPResult<List<DeviceChannelTree>> tree(@PathVariable String deviceId) {
|
||||||
|
return WVPResult.Data(storager.tree(deviceId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
19
web_src/src/api/deviceApi.js
Normal file
19
web_src/src/api/deviceApi.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const tree = (deviceId) => {
|
||||||
|
return axios({
|
||||||
|
url: `/api/device/query/${deviceId}/tree`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deviceList = (page, count) => {
|
||||||
|
return axios({
|
||||||
|
method: 'get',
|
||||||
|
url:`/api/device/query/devices`,
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
<div id="UiHeader">
|
<div id="UiHeader">
|
||||||
<el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
|
<el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
|
||||||
<el-menu-item index="/">控制台</el-menu-item>
|
<el-menu-item index="/">控制台</el-menu-item>
|
||||||
|
<el-menu-item index="/live">实时监控</el-menu-item>
|
||||||
<el-menu-item index="/deviceList">设备列表</el-menu-item>
|
<el-menu-item index="/deviceList">设备列表</el-menu-item>
|
||||||
<el-menu-item index="/pushVideoList">推流列表</el-menu-item>
|
<el-menu-item index="/pushVideoList">推流列表</el-menu-item>
|
||||||
<el-menu-item index="/streamProxyList">拉流代理</el-menu-item>
|
<el-menu-item index="/streamProxyList">拉流代理</el-menu-item>
|
||||||
|
70
web_src/src/components/channelTree.vue
Normal file
70
web_src/src/components/channelTree.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-tree :data="channelList" :props="props" @node-click="sendDevicePush">
|
||||||
|
<span slot-scope="{ node }">
|
||||||
|
<span v-if="node.isLeaf">
|
||||||
|
<i class="el-icon-video-camera" :style="{color:node.disabled==1?'#67C23A':'#F56C6C'}"></i>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<i class="el-icon-folder"></i>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ node.label }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</el-tree>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ChannelTreeItem from "@/components/channelTreeItem"
|
||||||
|
import {tree} from '@/api/deviceApi'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ChannelTreeItem,
|
||||||
|
},
|
||||||
|
props:{
|
||||||
|
device: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
channelList: [],
|
||||||
|
props: {
|
||||||
|
label: 'title',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: 'hasChildren',
|
||||||
|
disabled: 'status'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.leafs = []
|
||||||
|
this.getTree()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTree() {
|
||||||
|
this.loading = true
|
||||||
|
var that = this
|
||||||
|
tree(this.device.deviceId).then(function (res) {
|
||||||
|
console.log(res.data.data);
|
||||||
|
that.channelList = res.data.data;
|
||||||
|
that.loading = false;
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
that.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendDevicePush(c) {
|
||||||
|
if(c.hasChildren) return
|
||||||
|
this.$emit('sendDevicePush',c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
74
web_src/src/components/channelTreeItem.vue
Normal file
74
web_src/src/components/channelTreeItem.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- <div :index="item.key" v-for="(item,i) in list" :key="i+'-'">
|
||||||
|
<el-submenu v-if="item.hasChildren">
|
||||||
|
<template slot="title">
|
||||||
|
<i class="el-icon-video-camera"></i>
|
||||||
|
<span slot="title">{{item.title || item.deviceId}}</span>
|
||||||
|
</template>
|
||||||
|
<channel-list :list="item.children" @sendDevicePush="sendDevicePush"></channel-list>
|
||||||
|
</el-submenu>
|
||||||
|
<el-menu-item v-else :index="item.key" @click="sendDevicePush(item)">
|
||||||
|
<template slot="title" >
|
||||||
|
<i class="el-icon-switch-button" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i>
|
||||||
|
<span slot="title">{{item.title}}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</div> -->
|
||||||
|
<div >
|
||||||
|
<template v-if="!item.hasChildren">
|
||||||
|
<el-menu-item :index="item.key" @click="sendDevicePush(item)">
|
||||||
|
<i class="el-icon-video-camera" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i>
|
||||||
|
{{item.title}}
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-submenu v-else :index="item.key">
|
||||||
|
<template slot="title" >
|
||||||
|
<i class="el-icon-location-outline"></i>
|
||||||
|
{{item.title}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-for="child in item.children">
|
||||||
|
<channel-item
|
||||||
|
v-if="child.hasChildren"
|
||||||
|
:item="child"
|
||||||
|
:key="child.key"
|
||||||
|
@sendDevicePush="sendDevicePush"/>
|
||||||
|
<el-menu-item v-else :key="child.key" :index="child.key" @click="sendDevicePush(child)">
|
||||||
|
<i class="el-icon-video-camera" :style="{color:child.status==1?'#67C23A':'#F56C6C'}"></i>
|
||||||
|
{{child.title}}
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</el-submenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name:'ChannelItem',
|
||||||
|
props:{
|
||||||
|
list:Array,
|
||||||
|
channelId: String,
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
channelId(val) {
|
||||||
|
console.log(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sendDevicePush(c) {
|
||||||
|
this.$emit('sendDevicePush',c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
317
web_src/src/components/jessibuca.vue
Normal file
317
web_src/src/components/jessibuca.vue
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<template>
|
||||||
|
<div :id="'jessibuca'+idx" style="width: 100%; height: 100%">
|
||||||
|
<div :id="'container'+idx" ref="container" style="width: 100%; height: 100%; background-color: #000" @dblclick="fullscreenSwich">
|
||||||
|
<div class="buttons-box" :id="'buttonsBox'+idx">
|
||||||
|
<div class="buttons-box-left">
|
||||||
|
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
|
||||||
|
<i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause"></i>
|
||||||
|
<i class="iconfont icon-stop jessibuca-btn" @click="destroyButton"></i>
|
||||||
|
<i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="jessibuca.mute()"></i>
|
||||||
|
<i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="jessibuca.cancelMute()"></i>
|
||||||
|
</div>
|
||||||
|
<div class="buttons-box-right">
|
||||||
|
<span class="jessibuca-btn">{{kBps}} kb/s</span>
|
||||||
|
<!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
|
||||||
|
<!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
|
||||||
|
<i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="screenshot" style="font-size: 1rem !important"></i>
|
||||||
|
<i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i>
|
||||||
|
<i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i>
|
||||||
|
<i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'jessibuca',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
jessibuca: null,
|
||||||
|
playing: false,
|
||||||
|
isNotMute: false,
|
||||||
|
quieting: false,
|
||||||
|
fullscreen: false,
|
||||||
|
loaded: false, // mute
|
||||||
|
speed: 0,
|
||||||
|
performance: "", // 工作情况
|
||||||
|
kBps: 0,
|
||||||
|
btnDom: null,
|
||||||
|
videoInfo: null,
|
||||||
|
volume: 1,
|
||||||
|
rotate: 0,
|
||||||
|
vod: true, // 点播
|
||||||
|
forceNoOffscreen: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['videoUrl', 'error', 'hasAudio', 'height','idx'],
|
||||||
|
mounted () {
|
||||||
|
window.onerror = (msg) => {
|
||||||
|
// console.error(msg)
|
||||||
|
};
|
||||||
|
let paramUrl = decodeURIComponent(this.$route.params.url)
|
||||||
|
this.$nextTick(() =>{
|
||||||
|
let dom = document.getElementById("container"+this.idx);
|
||||||
|
// dom.style.height = (9/16 ) * dom.clientWidth + "px"
|
||||||
|
if (typeof (this.videoUrl) == "undefined") {
|
||||||
|
this.videoUrl = paramUrl;
|
||||||
|
}
|
||||||
|
this.btnDom = document.getElementById("buttonsBox"+this.idx);
|
||||||
|
console.log("初始化时的地址为: " + this.videoUrl)
|
||||||
|
this.play(this.videoUrl)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
videoUrl(newData, oldData){
|
||||||
|
this.play(newData)
|
||||||
|
},
|
||||||
|
immediate:true
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
create(){
|
||||||
|
let options = {};
|
||||||
|
console.log(this.$refs.container)
|
||||||
|
console.log("hasAudio " + !!this.hasAudio)
|
||||||
|
|
||||||
|
this.jessibuca = new window.Jessibuca(Object.assign(
|
||||||
|
{
|
||||||
|
container: this.$refs.container,
|
||||||
|
videoBuffer: 0.2, // 最大缓冲时长,单位秒
|
||||||
|
isResize: true,
|
||||||
|
decoder: "./static/js/jessibuca/index.js",
|
||||||
|
// text: "WVP-PRO",
|
||||||
|
// background: "bg.jpg",
|
||||||
|
loadingText: "加载中",
|
||||||
|
hasAudio: !!this.hasAudio,
|
||||||
|
debug: false,
|
||||||
|
timeout:5,
|
||||||
|
supportDblclickFullscreen: false, // 是否支持屏幕的双击事件,触发全屏,取消全屏事件。
|
||||||
|
operateBtns: {
|
||||||
|
fullscreen: false,
|
||||||
|
screenshot: false,
|
||||||
|
play: false,
|
||||||
|
audio: false,
|
||||||
|
},
|
||||||
|
record: "record",
|
||||||
|
vod: this.vod,
|
||||||
|
forceNoOffscreen: this.forceNoOffscreen,
|
||||||
|
isNotMute: this.isNotMute,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
));
|
||||||
|
|
||||||
|
let _this = this;
|
||||||
|
this.jessibuca.on("load", function () {
|
||||||
|
console.log("on load init");
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("log", function (msg) {
|
||||||
|
console.log("on log", msg);
|
||||||
|
});
|
||||||
|
this.jessibuca.on("record", function (msg) {
|
||||||
|
console.log("on record:", msg);
|
||||||
|
});
|
||||||
|
this.jessibuca.on("pause", function () {
|
||||||
|
_this.playing = false;
|
||||||
|
});
|
||||||
|
this.jessibuca.on("play", function () {
|
||||||
|
_this.playing = true;
|
||||||
|
});
|
||||||
|
this.jessibuca.on("fullscreen", function (msg) {
|
||||||
|
console.log("on fullscreen", msg);
|
||||||
|
_this.fullscreen = msg
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("mute", function (msg) {
|
||||||
|
console.log("on mute", msg);
|
||||||
|
_this.isNotMute = !msg;
|
||||||
|
});
|
||||||
|
this.jessibuca.on("audioInfo", function (msg) {
|
||||||
|
// console.log("audioInfo", msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("videoInfo", function (msg) {
|
||||||
|
this.videoInfo = msg;
|
||||||
|
// console.log("videoInfo", msg);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("bps", function (bps) {
|
||||||
|
// console.log('bps', bps);
|
||||||
|
|
||||||
|
});
|
||||||
|
let _ts = 0;
|
||||||
|
this.jessibuca.on("timeUpdate", function (ts) {
|
||||||
|
// console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts);
|
||||||
|
_ts = ts;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("videoInfo", function (info) {
|
||||||
|
console.log("videoInfo", info);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("error", (error) =>{
|
||||||
|
console.log("error", error);
|
||||||
|
this.pause()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on("timeout", ()=> {
|
||||||
|
console.log("timeout");
|
||||||
|
// this.pause()
|
||||||
|
this.play(this.videoUrl)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jessibuca.on('start', function () {
|
||||||
|
console.log('start');
|
||||||
|
})
|
||||||
|
|
||||||
|
this.jessibuca.on("performance", function (performance) {
|
||||||
|
let show = "卡顿";
|
||||||
|
if (performance === 2) {
|
||||||
|
show = "非常流畅";
|
||||||
|
} else if (performance === 1) {
|
||||||
|
show = "流畅";
|
||||||
|
}
|
||||||
|
_this.performance = show;
|
||||||
|
});
|
||||||
|
this.jessibuca.on('buffer', function (buffer) {
|
||||||
|
// console.log('buffer', buffer);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.jessibuca.on('stats', function (stats) {
|
||||||
|
// console.log('stats', stats);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.jessibuca.on('kBps', function (kBps) {
|
||||||
|
_this.kBps = Math.round(kBps);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示时间戳 PTS
|
||||||
|
this.jessibuca.on('videoFrame', function () {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
this.jessibuca.on('metadata', function () {
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
playBtnClick: function (event){
|
||||||
|
this.play(this.videoUrl)
|
||||||
|
},
|
||||||
|
play: function (url) {
|
||||||
|
console.log(url)
|
||||||
|
|
||||||
|
if (this.jessibuca) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
if(!url){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.create();
|
||||||
|
this.jessibuca.on("play", () => {
|
||||||
|
this.playing = true;
|
||||||
|
this.loaded = true;
|
||||||
|
this.quieting = this.jessibuca.quieting;
|
||||||
|
});
|
||||||
|
if (this.jessibuca.hasLoaded()) {
|
||||||
|
this.jessibuca.play(url);
|
||||||
|
} else {
|
||||||
|
this.jessibuca.on("load", () => {
|
||||||
|
console.log("load 播放")
|
||||||
|
this.jessibuca.play(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pause: function () {
|
||||||
|
if (this.jessibuca) {
|
||||||
|
this.jessibuca.pause();
|
||||||
|
}
|
||||||
|
this.playing = false;
|
||||||
|
this.err = "";
|
||||||
|
this.performance = "";
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
if (this.jessibuca) {
|
||||||
|
this.jessibuca.destroy();
|
||||||
|
}
|
||||||
|
if (document.getElementById("buttonsBox"+this.idx) == null) {
|
||||||
|
document.getElementById("container"+this.idx).appendChild(this.btnDom)
|
||||||
|
}
|
||||||
|
this.jessibuca = null;
|
||||||
|
this.playing = false;
|
||||||
|
this.err = "";
|
||||||
|
this.performance = "";
|
||||||
|
|
||||||
|
},
|
||||||
|
eventcallbacK: function(type, message) {
|
||||||
|
// console.log("player 事件回调")
|
||||||
|
// console.log(type)
|
||||||
|
// console.log(message)
|
||||||
|
},
|
||||||
|
fullscreenSwich: function (){
|
||||||
|
let isFull = this.isFullscreen()
|
||||||
|
this.jessibuca.setFullscreen(!isFull)
|
||||||
|
this.fullscreen = !isFull;
|
||||||
|
},
|
||||||
|
isFullscreen: function (){
|
||||||
|
return document.fullscreenElement ||
|
||||||
|
document.msFullscreenElement ||
|
||||||
|
document.mozFullScreenElement ||
|
||||||
|
document.webkitFullscreenElement || false;
|
||||||
|
},
|
||||||
|
resize(){
|
||||||
|
this.jessibuca.resize()
|
||||||
|
},
|
||||||
|
screenshot(){
|
||||||
|
this.jessibuca.screenshot('截图','png',0.5)
|
||||||
|
// let base64 = this.jessibuca.screenshot("shot","jpeg",0.5,'base64')
|
||||||
|
// this.$emit('screenshot',base64)
|
||||||
|
},
|
||||||
|
destroyButton() {
|
||||||
|
this.$emit('destroy', this.idx)
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
if (this.jessibuca) {
|
||||||
|
this.jessibuca.destroy();
|
||||||
|
}
|
||||||
|
this.playing = false;
|
||||||
|
this.loaded = false;
|
||||||
|
this.performance = "";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.buttons-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 28px;
|
||||||
|
background-color: rgba(43, 51, 63, 0.7);
|
||||||
|
position: absolute;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.jessibuca-btn{
|
||||||
|
width: 20px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
line-height: 27px;
|
||||||
|
margin: 0px 10px;
|
||||||
|
padding: 0px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
}
|
||||||
|
.buttons-box-right {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
357
web_src/src/components/live.vue
Normal file
357
web_src/src/components/live.vue
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
<template>
|
||||||
|
<div id="devicePosition" style="height: 100%">
|
||||||
|
<el-container style="height: 100%">
|
||||||
|
<el-header>
|
||||||
|
<uiHeader></uiHeader>
|
||||||
|
</el-header>
|
||||||
|
<el-container v-loading="loading" element-loading-text="拼命加载中">
|
||||||
|
<el-aside width="300px" style="background-color: #ffffff">
|
||||||
|
<div style="text-align: center;padding-top: 20px;">设备列表</div>
|
||||||
|
<el-menu v-loading="loading">
|
||||||
|
<el-submenu v-for="device in deviceList" :key="device.deviceId" :index="device.deviceId" @click="sendDevicePush(item)">
|
||||||
|
<template slot="title" >
|
||||||
|
<i class="el-icon-location-outline"></i>
|
||||||
|
{{device.name}}
|
||||||
|
</template>
|
||||||
|
<ChannelTree :device="device" @sendDevicePush="sendDevicePush"></ChannelTree>
|
||||||
|
</el-submenu>
|
||||||
|
</el-menu>
|
||||||
|
</el-aside>
|
||||||
|
<el-container>
|
||||||
|
<!-- <LivePlay></LivePlay> -->
|
||||||
|
<el-header height="40px" style="text-align: left;font-size: 17px;line-height: 40px;">
|
||||||
|
分屏:
|
||||||
|
<i class="el-icon-full-screen btn" :class="{active:spilt==1}" @click="spilt=1"/>
|
||||||
|
<i class="el-icon-menu btn" :class="{active:spilt==4}" @click="spilt=4"/>
|
||||||
|
<i class="el-icon-s-grid btn" :class="{active:spilt==9}" @click="spilt=9"/>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<div style="width: 100%;height: calc( 100vh - 110px );display: flex;flex-wrap: wrap;background-color: #000;">
|
||||||
|
<div v-for="i in spilt" :key="i" class="play-box"
|
||||||
|
:style="liveStyle" :class="{redborder:playerIdx == (i-1)}"
|
||||||
|
@click="playerIdx = (i-1)"
|
||||||
|
>
|
||||||
|
<div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 30px;font-weight: bold;">{{i}}</div>
|
||||||
|
<player v-else :ref="'player'+i" :videoUrl="videoUrl[i-1]" fluent autoplay :height="true"
|
||||||
|
:idx="'player'+i" @screenshot="shot" @destroy="destroy"></player>
|
||||||
|
<!-- <player v-else ref="'player'+i" :idx="'player'+i" :visible.sync="showVideoDialog" :videoUrl="videoUrl[i-1]" :height="true" :hasAudio="hasAudio" fluent autoplay live ></player> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import uiHeader from "./UiHeader.vue";
|
||||||
|
import player from './jessibuca.vue'
|
||||||
|
import ChannelTree from './channelTree.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "live",
|
||||||
|
components: {
|
||||||
|
uiHeader, player, ChannelTree
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showVideoDialog: true,
|
||||||
|
hasAudio: false,
|
||||||
|
videoUrl:[''],
|
||||||
|
spilt:1,//分屏
|
||||||
|
playerIdx:0,//激活播放器
|
||||||
|
|
||||||
|
deviceList: [], //设备列表
|
||||||
|
currentDevice: {}, //当前操作设备对象
|
||||||
|
|
||||||
|
videoComponentList: [],
|
||||||
|
updateLooper: 0, //数据刷新轮训标志
|
||||||
|
currentDeviceChannelsLenth:0,
|
||||||
|
winHeight: window.innerHeight - 200,
|
||||||
|
currentPage:1,
|
||||||
|
count:15,
|
||||||
|
total:0,
|
||||||
|
getDeviceListLoading: false,
|
||||||
|
|
||||||
|
//channel
|
||||||
|
searchSrt: "",
|
||||||
|
channelType: "",
|
||||||
|
online: "",
|
||||||
|
channelTotal:0,
|
||||||
|
deviceChannelList:[],
|
||||||
|
loading:false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initData();
|
||||||
|
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.checkPlayByParam()
|
||||||
|
},
|
||||||
|
|
||||||
|
computed:{
|
||||||
|
liveStyle(){
|
||||||
|
if(this.spilt==1){
|
||||||
|
return {width:'100%',height:'100%'}
|
||||||
|
}else if(this.spilt==4){
|
||||||
|
return {width:'49%',height:'49%'}
|
||||||
|
}else if(this.spilt==9){
|
||||||
|
return {width:'32%',height:'32%'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
spilt(newValue){
|
||||||
|
console.log("切换画幅;"+newValue)
|
||||||
|
let that = this
|
||||||
|
for (let i = 1; i <= newValue; i++) {
|
||||||
|
if(!that.$refs['player'+i]){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
if(that.$refs['player'+i] instanceof Array){
|
||||||
|
that.$refs['player'+i][0].resize()
|
||||||
|
}else {
|
||||||
|
that.$refs['player'+i].resize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
window.localStorage.setItem('split',newValue)
|
||||||
|
},
|
||||||
|
'$route.fullPath':'checkPlayByParam'
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
clearTimeout(this.updateLooper);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData: function () {
|
||||||
|
this.getDeviceList();
|
||||||
|
|
||||||
|
},
|
||||||
|
destroy(idx) {
|
||||||
|
console.log(idx);
|
||||||
|
this.clear(idx.substring(idx.length-1))
|
||||||
|
},
|
||||||
|
getDeviceList: function() {
|
||||||
|
let that = this;
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url:`/api/device/query/devices`,
|
||||||
|
params: {
|
||||||
|
page: that.currentPage,
|
||||||
|
count: that.count
|
||||||
|
}
|
||||||
|
}).then(function (res) {
|
||||||
|
console.log(res.data.list);
|
||||||
|
that.total = res.data.total;
|
||||||
|
|
||||||
|
that.deviceList = res.data.list.map(item=>{return {deviceChannelList:[],...item}});
|
||||||
|
that.getDeviceListLoading = false;
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
that.getDeviceListLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//通知设备上传媒体流
|
||||||
|
sendDevicePush: function (itemData) {
|
||||||
|
if(itemData.status===0){
|
||||||
|
this.$message.error('设备离线!');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.save(itemData)
|
||||||
|
let deviceId = itemData.deviceId;
|
||||||
|
// this.isLoging = true;
|
||||||
|
let channelId = itemData.channelId;
|
||||||
|
console.log("通知设备推流1:" + deviceId + " : " + channelId );
|
||||||
|
let idxTmp = this.playerIdx
|
||||||
|
let that = this;
|
||||||
|
this.loading = true
|
||||||
|
this.$axios({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/play/start/' + deviceId + '/' + channelId
|
||||||
|
}).then(function (res) {
|
||||||
|
// that.isLoging = false;
|
||||||
|
console.log('=====----=====')
|
||||||
|
console.log(res)
|
||||||
|
if (res.data.code == 0 && res.data.data) {
|
||||||
|
itemData.playUrl = res.data.data.httpsFlv
|
||||||
|
that.setPlayUrl(res.data.data.ws_flv,idxTmp)
|
||||||
|
}else {
|
||||||
|
that.$message.error(res.data.msg);
|
||||||
|
}
|
||||||
|
}).catch(function (e) {
|
||||||
|
}).finally(()=>{
|
||||||
|
that.loading = false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setPlayUrl(url,idx){
|
||||||
|
this.$set(this.videoUrl,idx,url)
|
||||||
|
let _this = this
|
||||||
|
setTimeout(()=>{
|
||||||
|
window.localStorage.setItem('videoUrl',JSON.stringify(_this.videoUrl))
|
||||||
|
},100)
|
||||||
|
|
||||||
|
},
|
||||||
|
checkPlayByParam(){
|
||||||
|
let {deviceId,channelId} = this.$route.query
|
||||||
|
if(deviceId && channelId){
|
||||||
|
this.sendDevicePush({deviceId,channelId})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
convertImageToCanvas(image) {
|
||||||
|
var canvas = document.createElement("canvas");
|
||||||
|
canvas.width = image.width;
|
||||||
|
canvas.height = image.height;
|
||||||
|
canvas.getContext("2d").drawImage(image, 0, 0);
|
||||||
|
return canvas;
|
||||||
|
},
|
||||||
|
shot(e){
|
||||||
|
// console.log(e)
|
||||||
|
// send({code:'image',data:e})
|
||||||
|
var base64ToBlob = function(code) {
|
||||||
|
let parts = code.split(';base64,');
|
||||||
|
let contentType = parts[0].split(':')[1];
|
||||||
|
let raw = window.atob(parts[1]);
|
||||||
|
let rawLength = raw.length;
|
||||||
|
let uInt8Array = new Uint8Array(rawLength);
|
||||||
|
for(let i = 0; i < rawLength; ++i) {
|
||||||
|
uInt8Array[i] = raw.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new Blob([uInt8Array], {
|
||||||
|
type: contentType
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let aLink = document.createElement('a');
|
||||||
|
let blob = base64ToBlob(e); //new Blob([content]);
|
||||||
|
let evt = document.createEvent("HTMLEvents");
|
||||||
|
evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
|
||||||
|
aLink.download = '截图';
|
||||||
|
aLink.href = URL.createObjectURL(blob);
|
||||||
|
aLink.click();
|
||||||
|
},
|
||||||
|
save(item){
|
||||||
|
let dataStr = window.localStorage.getItem('playData') || '[]'
|
||||||
|
let data = JSON.parse(dataStr);
|
||||||
|
data[this.playerIdx] = item
|
||||||
|
window.localStorage.setItem('playData',JSON.stringify(data))
|
||||||
|
},
|
||||||
|
clear(idx) {
|
||||||
|
let dataStr = window.localStorage.getItem('playData') || '[]'
|
||||||
|
let data = JSON.parse(dataStr);
|
||||||
|
data[idx-1] = null;
|
||||||
|
console.log(data);
|
||||||
|
window.localStorage.setItem('playData',JSON.stringify(data))
|
||||||
|
},
|
||||||
|
loadAndPlay(){
|
||||||
|
let dataStr = window.localStorage.getItem('playData') || '[]'
|
||||||
|
let data = JSON.parse(dataStr);
|
||||||
|
|
||||||
|
data.forEach((item,i)=>{
|
||||||
|
if(item){
|
||||||
|
this.playerIdx = i
|
||||||
|
this.sendDevicePush(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.btn{
|
||||||
|
margin: 0 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.btn:hover{
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
.btn.active{
|
||||||
|
color: #409EFF;
|
||||||
|
|
||||||
|
}
|
||||||
|
.redborder{
|
||||||
|
border: 2px solid red !important;
|
||||||
|
}
|
||||||
|
.play-box{
|
||||||
|
background-color: #000000;
|
||||||
|
border: 2px solid #505050;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.videoList {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-item {
|
||||||
|
position: relative;
|
||||||
|
width: 15rem;
|
||||||
|
height: 10rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-item-img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-item-img:after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-image: url("../assets/loading.png");
|
||||||
|
background-size: cover;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-item-title {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ffffff;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
padding: 0.3rem;
|
||||||
|
width: 14.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.baidumap {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去除百度地图版权那行字 和 百度logo */
|
||||||
|
.baidumap > .BMap_cpyCtrl {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.baidumap > .anchorBL {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,6 +15,7 @@ import test from '../components/test.vue'
|
|||||||
import web from '../components/setting/Web.vue'
|
import web from '../components/setting/Web.vue'
|
||||||
import sip from '../components/setting/Sip.vue'
|
import sip from '../components/setting/Sip.vue'
|
||||||
import media from '../components/setting/Media.vue'
|
import media from '../components/setting/Media.vue'
|
||||||
|
import live from '../components/live.vue'
|
||||||
|
|
||||||
import wasmPlayer from '../components/dialog/jessibuca.vue'
|
import wasmPlayer from '../components/dialog/jessibuca.vue'
|
||||||
import rtcPlayer from '../components/dialog/rtcPlayer.vue'
|
import rtcPlayer from '../components/dialog/rtcPlayer.vue'
|
||||||
@ -34,6 +35,10 @@ export default new VueRouter({
|
|||||||
path: '/',
|
path: '/',
|
||||||
component: control,
|
component: control,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/live',
|
||||||
|
component: live,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/deviceList',
|
path: '/deviceList',
|
||||||
component: deviceList,
|
component: deviceList,
|
||||||
|
Loading…
Reference in New Issue
Block a user