Commit 37a84e66917d6e22e03e31b0a115e2c16d23ed21

Authored by 朱俊杰
1 parent d07a5680

新增实时监控功能

左边是设备通道树,右边是分屏预览
... ... @@ -277,5 +277,16 @@
277 277 </plugin>
278 278  
279 279 </plugins>
  280 + <resources>
  281 + <resource>
  282 + <directory>src/main/resources</directory>
  283 + </resource>
  284 + <resource>
  285 + <directory>src/main/java</directory>
  286 + <includes>
  287 + <include>**/*.xml</include>
  288 + </includes>
  289 + </resource>
  290 + </resources>
280 291 </build>
281 292 </project>
... ...
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
... ... @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
5 5 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
6 6 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
7 7 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
  8 +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
8 9 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
9 10 import com.github.pagehelper.PageInfo;
10 11  
... ... @@ -94,6 +95,13 @@ public interface IVideoManagerStorager {
94 95 public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
95 96  
96 97 /**
  98 + * 获取某个设备的通道树
  99 + * @param deviceId 设备ID
  100 + * @return
  101 + */
  102 + List<DeviceChannelTree> tree(String deviceId);
  103 +
  104 + /**
97 105 * 获取某个设备的通道列表
98 106 *
99 107 * @param deviceId 设备ID
... ...
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
1 1 package com.genersoft.iot.vmp.storager.dao;
2 2  
3 3 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  4 +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
4 5 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
5 6 import org.apache.ibatis.annotations.*;
6 7 import org.springframework.stereotype.Repository;
... ... @@ -201,4 +202,6 @@ public interface DeviceChannelMapper {
201 202  
202 203 @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND status=1")
203 204 List<DeviceChannel> queryOnlineChannelsByDeviceId(String deviceId);
  205 +
  206 + List<DeviceChannelTree> tree(String deviceId);
204 207 }
... ...
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper">
  4 +
  5 + <!-- 通用查询映射结果 -->
  6 + <resultMap id="treeNodeResultMap" type="com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTreeNode">
  7 + <id column="id" property="id"/>
  8 + <result column="parentId" property="parentId"/>
  9 + <result column="status" property="status"/>
  10 + <result column="title" property="title"/>
  11 + <result column="value" property="value"/>
  12 + <result column="key" property="key"/>
  13 + <result column="deviceId" property="deviceId"/>
  14 + <result column="channelId" property="channelId"/>
  15 + <result column="longitude" property="lng"/>
  16 + <result column="latitude" property="lat"/>
  17 + </resultMap>
  18 +
  19 +
  20 + <select id="tree" resultMap="treeNodeResultMap">
  21 + SELECT
  22 + channelId,
  23 + channelId as id,
  24 + deviceId,
  25 + parentId,
  26 + status,
  27 + name as title,
  28 + channelId as "value",
  29 + channelId as "key",
  30 + channelId,
  31 + longitude,
  32 + latitude
  33 + from device_channel
  34 + where deviceId = #{deviceId}
  35 + </select>
  36 +
  37 +</mapper>
... ...
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
... ... @@ -13,6 +13,8 @@ import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
13 13 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
14 14 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
15 15 import com.genersoft.iot.vmp.storager.dao.*;
  16 +import com.genersoft.iot.vmp.utils.node.ForestNodeMerger;
  17 +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
16 18 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
17 19 import com.github.pagehelper.PageHelper;
18 20 import com.github.pagehelper.PageInfo;
... ... @@ -329,6 +331,11 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
329 331 }
330 332  
331 333 @Override
  334 + public List<DeviceChannelTree> tree(String deviceId) {
  335 + return ForestNodeMerger.merge(deviceChannelMapper.tree(deviceId));
  336 + }
  337 +
  338 + @Override
332 339 public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
333 340 return deviceChannelMapper.queryChannels(deviceId, null,null, null, null);
334 341 }
... ...
src/main/java/com/genersoft/iot/vmp/utils/CollectionUtil.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils;
  2 +
  3 +import java.util.Arrays;
  4 +
  5 +public class CollectionUtil {
  6 +
  7 + public static <T> boolean contains(T[] array, final T element) {
  8 + return array != null && Arrays.stream(array).anyMatch((x) -> {
  9 + return ObjectUtils.nullSafeEquals(x, element);
  10 + });
  11 + }
  12 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/ObjectUtils.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils;
  2 +
  3 +import java.util.Arrays;
  4 +
  5 +public class ObjectUtils {
  6 + public static boolean nullSafeEquals(Object o1, Object o2) {
  7 + if (o1 == o2) {
  8 + return true;
  9 + } else if (o1 != null && o2 != null) {
  10 + if (o1.equals(o2)) {
  11 + return true;
  12 + } else {
  13 + return o1.getClass().isArray() && o2.getClass().isArray() && arrayEquals(o1, o2);
  14 + }
  15 + } else {
  16 + return false;
  17 + }
  18 + }
  19 +
  20 + private static boolean arrayEquals(Object o1, Object o2) {
  21 + if (o1 instanceof Object[] && o2 instanceof Object[]) {
  22 + return Arrays.equals((Object[])((Object[])o1), (Object[])((Object[])o2));
  23 + } else if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
  24 + return Arrays.equals((boolean[])((boolean[])o1), (boolean[])((boolean[])o2));
  25 + } else if (o1 instanceof byte[] && o2 instanceof byte[]) {
  26 + return Arrays.equals((byte[])((byte[])o1), (byte[])((byte[])o2));
  27 + } else if (o1 instanceof char[] && o2 instanceof char[]) {
  28 + return Arrays.equals((char[])((char[])o1), (char[])((char[])o2));
  29 + } else if (o1 instanceof double[] && o2 instanceof double[]) {
  30 + return Arrays.equals((double[])((double[])o1), (double[])((double[])o2));
  31 + } else if (o1 instanceof float[] && o2 instanceof float[]) {
  32 + return Arrays.equals((float[])((float[])o1), (float[])((float[])o2));
  33 + } else if (o1 instanceof int[] && o2 instanceof int[]) {
  34 + return Arrays.equals((int[])((int[])o1), (int[])((int[])o2));
  35 + } else if (o1 instanceof long[] && o2 instanceof long[]) {
  36 + return Arrays.equals((long[])((long[])o1), (long[])((long[])o2));
  37 + } else {
  38 + return o1 instanceof short[] && o2 instanceof short[] && Arrays.equals((short[]) ((short[]) o1), (short[]) ((short[]) o2));
  39 + }
  40 + }
  41 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/BaseNode.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import lombok.Data;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 节点基类
  11 + *
  12 + */
  13 +@Data
  14 +public class BaseNode<T> implements INode<T> {
  15 +
  16 + private static final long serialVersionUID = 1L;
  17 +
  18 + /**
  19 + * 主键ID
  20 + */
  21 + protected String id;
  22 +
  23 + /**
  24 + * 父节点ID
  25 + */
  26 + protected String parentId;
  27 +
  28 + /**
  29 + * 子孙节点
  30 + */
  31 + @JsonInclude(JsonInclude.Include.NON_EMPTY)
  32 + protected List<T> children = new ArrayList<T>();
  33 +
  34 + /**
  35 + * 是否有子孙节点
  36 + */
  37 + @JsonInclude(JsonInclude.Include.NON_EMPTY)
  38 + private Boolean hasChildren;
  39 +
  40 + /**
  41 + * 是否有子孙节点
  42 + *
  43 + * @return Boolean
  44 + */
  45 + @Override
  46 + public Boolean getHasChildren() {
  47 + if (children.size() > 0) {
  48 + return true;
  49 + } else {
  50 + return this.hasChildren;
  51 + }
  52 + }
  53 +
  54 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/ForestNode.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import lombok.Data;
  4 +import lombok.EqualsAndHashCode;
  5 +
  6 +
  7 +/**
  8 + * 森林节点类
  9 + *
  10 + */
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = false)
  13 +public class ForestNode extends BaseNode<ForestNode> {
  14 +
  15 + private static final long serialVersionUID = 1L;
  16 +
  17 + /**
  18 + * 节点内容
  19 + */
  20 + private Object content;
  21 +
  22 + public ForestNode(String id, String parentId, Object content) {
  23 + this.id = id;
  24 + this.parentId = parentId;
  25 + this.content = content;
  26 + }
  27 +
  28 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeManager.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import com.google.common.collect.ImmutableMap;
  4 +import com.google.common.collect.Maps;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * 森林管理类
  12 + *
  13 + * @author smallchill
  14 + */
  15 +public class ForestNodeManager<T extends INode<T>> {
  16 +
  17 + /**
  18 + * 森林的所有节点
  19 + */
  20 + private final ImmutableMap<String, T> nodeMap;
  21 +
  22 + /**
  23 + * 森林的父节点ID
  24 + */
  25 + private final Map<String, Object> parentIdMap = Maps.newHashMap();
  26 +
  27 + public ForestNodeManager(List<T> nodes) {
  28 + nodeMap = Maps.uniqueIndex(nodes, INode::getId);
  29 + }
  30 +
  31 + /**
  32 + * 根据节点ID获取一个节点
  33 + *
  34 + * @param id 节点ID
  35 + * @return 对应的节点对象
  36 + */
  37 + public INode<T> getTreeNodeAt(String id) {
  38 + if (nodeMap.containsKey(id)) {
  39 + return nodeMap.get(id);
  40 + }
  41 + return null;
  42 + }
  43 +
  44 + /**
  45 + * 增加父节点ID
  46 + *
  47 + * @param parentId 父节点ID
  48 + */
  49 + public void addParentId(String parentId) {
  50 + parentIdMap.put(parentId, "");
  51 + }
  52 +
  53 + /**
  54 + * 获取树的根节点(一个森林对应多颗树)
  55 + *
  56 + * @return 树的根节点集合
  57 + */
  58 + public List<T> getRoot() {
  59 + List<T> roots = new ArrayList<>();
  60 + nodeMap.forEach((key, node) -> {
  61 + if (node.getParentId() == null || parentIdMap.containsKey(node.getId())) {
  62 + roots.add(node);
  63 + }
  64 + });
  65 + return roots;
  66 + }
  67 +
  68 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/ForestNodeMerger.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import com.genersoft.iot.vmp.utils.CollectionUtil;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * 森林节点归并类
  9 + *
  10 + */
  11 +public class ForestNodeMerger {
  12 +
  13 + /**
  14 + * 将节点数组归并为一个森林(多棵树)(填充节点的children域)
  15 + * 时间复杂度为O(n^2)
  16 + *
  17 + * @param items 节点域
  18 + * @return 多棵树的根节点集合
  19 + */
  20 + public static <T extends INode<T>> List<T> merge(List<T> items) {
  21 + ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items);
  22 + items.forEach(forestNode -> {
  23 + if (forestNode.getParentId() != null) {
  24 + INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
  25 + if (node != null) {
  26 + node.getChildren().add(forestNode);
  27 + } else {
  28 + forestNodeManager.addParentId(forestNode.getId());
  29 + }
  30 + }
  31 + });
  32 + return forestNodeManager.getRoot();
  33 + }
  34 +
  35 + public static <T extends INode<T>> List<T> merge(List<T> items, String[] parentIds) {
  36 + ForestNodeManager<T> forestNodeManager = new ForestNodeManager<>(items);
  37 + items.forEach(forestNode -> {
  38 + if (forestNode.getParentId() != null) {
  39 + INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
  40 + if (CollectionUtil.contains(parentIds, forestNode.getId())){
  41 + forestNodeManager.addParentId(forestNode.getId());
  42 + } else {
  43 + if (node != null){
  44 + node.getChildren().add(forestNode);
  45 + }
  46 + }
  47 + }
  48 + });
  49 + return forestNodeManager.getRoot();
  50 + }
  51 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/INode.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import java.io.Serializable;
  4 +import java.util.List;
  5 +
  6 +/**
  7 + *
  8 + * 节点
  9 + */
  10 +public interface INode<T> extends Serializable {
  11 +
  12 + /**
  13 + * 主键
  14 + *
  15 + * @return String
  16 + */
  17 + String getId();
  18 +
  19 + /**
  20 + * 父主键
  21 + *
  22 + * @return String
  23 + */
  24 + String getParentId();
  25 +
  26 + /**
  27 + * 子孙节点
  28 + *
  29 + * @return List<T>
  30 + */
  31 + List<T> getChildren();
  32 +
  33 + /**
  34 + * 是否有子孙节点
  35 + *
  36 + * @return Boolean
  37 + */
  38 + default Boolean getHasChildren() {
  39 + return false;
  40 + }
  41 +
  42 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/node/TreeNode.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils.node;
  2 +
  3 +import lombok.Data;
  4 +import lombok.EqualsAndHashCode;
  5 +
  6 +/**
  7 + * 树型节点类
  8 + *
  9 + */
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = false)
  12 +public class TreeNode extends BaseNode<TreeNode> {
  13 +
  14 + private static final long serialVersionUID = 1L;
  15 +
  16 + private String title;
  17 +
  18 + private String key;
  19 +
  20 + private String value;
  21 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTree.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.bean;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  5 +import com.genersoft.iot.vmp.utils.node.INode;
  6 +import io.swagger.annotations.ApiModel;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +
  10 +import java.util.ArrayList;
  11 +import java.util.List;
  12 +
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ApiModel(value = "DeviceChannelTree对象", description = "DeviceChannelTree对象")
  16 +public class DeviceChannelTree extends DeviceChannel implements INode<DeviceChannelTree> {
  17 + private static final long serialVersionUID = 1L;
  18 +
  19 + /**
  20 + * 主键ID
  21 + */
  22 + private String id;
  23 +
  24 + /**
  25 + * 父节点ID
  26 + */
  27 + private String parentId;
  28 +
  29 + private String parentName;
  30 +
  31 + /**
  32 + * 子孙节点
  33 + */
  34 + @JsonInclude(JsonInclude.Include.NON_EMPTY)
  35 + private List<DeviceChannelTree> children;
  36 +
  37 + /**
  38 + * 是否有子孙节点
  39 + */
  40 + @JsonInclude(JsonInclude.Include.NON_EMPTY)
  41 + private Boolean hasChildren;
  42 +
  43 + @Override
  44 + public List<DeviceChannelTree> getChildren() {
  45 + if (this.children == null) {
  46 + this.children = new ArrayList<>();
  47 + }
  48 + return this.children;
  49 + }
  50 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/bean/DeviceChannelTreeNode.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.bean;
  2 +
  3 +import com.genersoft.iot.vmp.utils.node.TreeNode;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +@Data
  8 +@EqualsAndHashCode(callSuper = true)
  9 +public class DeviceChannelTreeNode extends TreeNode {
  10 +
  11 + private Integer status;
  12 +
  13 + private String deviceId;
  14 +
  15 + private String channelId;
  16 +
  17 + private Double lng;
  18 +
  19 + private Double lat;
  20 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java
1 1 package com.genersoft.iot.vmp.vmanager.bean;
2 2  
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +@Data
  8 +@NoArgsConstructor
  9 +@AllArgsConstructor
3 10 public class WVPResult<T> {
4 11  
5 12 private int code;
6 13 private String msg;
7 14 private T data;
8 15  
9   - public int getCode() {
10   - return code;
11   - }
  16 + private static final Integer SUCCESS = 200;
  17 + private static final Integer FAILED = 400;
12 18  
13   - public void setCode(int code) {
14   - this.code = code;
  19 + public static <T> WVPResult<T> Data(T t, String msg) {
  20 + return new WVPResult<>(SUCCESS, msg, t);
15 21 }
16 22  
17   - public String getMsg() {
18   - return msg;
  23 + public static <T> WVPResult<T> Data(T t) {
  24 + return Data(t, "成功");
19 25 }
20 26  
21   - public void setMsg(String msg) {
22   - this.msg = msg;
  27 + public static <T> WVPResult<T> fail(int code, String msg) {
  28 + return new WVPResult<>(code, msg, null);
23 29 }
24 30  
25   - public T getData() {
26   - return data;
  31 + public static <T> WVPResult<T> fail(String msg) {
  32 + return fail(FAILED, msg);
27 33 }
28 34  
29   - public void setData(T data) {
30   - this.data = data;
31   - }
32 35 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
... ... @@ -10,8 +10,10 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
10 10 import com.genersoft.iot.vmp.service.IDeviceService;
11 11 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
12 12 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  13 +import com.genersoft.iot.vmp.vmanager.bean.DeviceChannelTree;
13 14 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
14 15 import com.github.pagehelper.PageInfo;
  16 +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
15 17 import io.swagger.annotations.Api;
16 18 import io.swagger.annotations.ApiImplicitParam;
17 19 import io.swagger.annotations.ApiImplicitParams;
... ... @@ -25,6 +27,7 @@ import org.springframework.util.StringUtils;
25 27 import org.springframework.web.bind.annotation.*;
26 28 import org.springframework.web.context.request.async.DeferredResult;
27 29  
  30 +import java.util.List;
28 31 import java.util.UUID;
29 32  
30 33 @Api(tags = "国标设备查询", value = "国标设备查询")
... ... @@ -431,5 +434,9 @@ public class DeviceQuery {
431 434 return result;
432 435 }
433 436  
434   -
  437 + @GetMapping("/{deviceId}/tree")
  438 + @ApiOperation(value = "通道树形结构", notes = "通道树形结构")
  439 + public WVPResult<List<DeviceChannelTree>> tree(@PathVariable String deviceId) {
  440 + return WVPResult.Data(storager.tree(deviceId));
  441 + }
435 442 }
... ...
web_src/src/api/deviceApi.js 0 → 100644
  1 +import axios from 'axios';
  2 +
  3 +export const tree = (deviceId) => {
  4 + return axios({
  5 + url: `/api/device/query/${deviceId}/tree`,
  6 + method: 'get'
  7 + })
  8 +}
  9 +
  10 +export const deviceList = (page, count) => {
  11 + return axios({
  12 + method: 'get',
  13 + url:`/api/device/query/devices`,
  14 + params: {
  15 + page,
  16 + count
  17 + }
  18 + })
  19 +}
0 20 \ No newline at end of file
... ...
web_src/src/components/UiHeader.vue
... ... @@ -2,6 +2,7 @@
2 2 <div id="UiHeader">
3 3 <el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" mode="horizontal">
4 4 <el-menu-item index="/">控制台</el-menu-item>
  5 + <el-menu-item index="/live">实时监控</el-menu-item>
5 6 <el-menu-item index="/deviceList">设备列表</el-menu-item>
6 7 <el-menu-item index="/pushVideoList">推流列表</el-menu-item>
7 8 <el-menu-item index="/streamProxyList">拉流代理</el-menu-item>
... ...
web_src/src/components/channelTree.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <el-tree :data="channelList" :props="props" @node-click="sendDevicePush">
  4 + <span slot-scope="{ node }">
  5 + <span v-if="node.isLeaf">
  6 + <i class="el-icon-video-camera" :style="{color:node.disabled==1?'#67C23A':'#F56C6C'}"></i>
  7 + </span>
  8 + <span v-else>
  9 + <i class="el-icon-folder"></i>
  10 + </span>
  11 + <span>
  12 + {{ node.label }}
  13 + </span>
  14 + </span>
  15 + </el-tree>
  16 + </div>
  17 +</template>
  18 +<script>
  19 +import ChannelTreeItem from "@/components/channelTreeItem"
  20 +import {tree} from '@/api/deviceApi'
  21 +
  22 +export default {
  23 + components: {
  24 + ChannelTreeItem,
  25 + },
  26 + props:{
  27 + device: {
  28 + type: Object,
  29 + required: true
  30 + }
  31 + },
  32 + data() {
  33 + return {
  34 + loading: false,
  35 + channelList: [],
  36 + props: {
  37 + label: 'title',
  38 + children: 'children',
  39 + isLeaf: 'hasChildren',
  40 + disabled: 'status'
  41 + },
  42 + }
  43 + },
  44 + computed: {
  45 +
  46 + },
  47 + mounted() {
  48 + this.leafs = []
  49 + this.getTree()
  50 + },
  51 + methods: {
  52 + getTree() {
  53 + this.loading = true
  54 + var that = this
  55 + tree(this.device.deviceId).then(function (res) {
  56 + console.log(res.data.data);
  57 + that.channelList = res.data.data;
  58 + that.loading = false;
  59 + }).catch(function (error) {
  60 + console.log(error);
  61 + that.loading = false;
  62 + });
  63 + },
  64 + sendDevicePush(c) {
  65 + if(c.hasChildren) return
  66 + this.$emit('sendDevicePush',c)
  67 + }
  68 + }
  69 +}
  70 +</script>
0 71 \ No newline at end of file
... ...
web_src/src/components/channelTreeItem.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <!-- <div :index="item.key" v-for="(item,i) in list" :key="i+'-'">
  4 + <el-submenu v-if="item.hasChildren">
  5 + <template slot="title">
  6 + <i class="el-icon-video-camera"></i>
  7 + <span slot="title">{{item.title || item.deviceId}}</span>
  8 + </template>
  9 + <channel-list :list="item.children" @sendDevicePush="sendDevicePush"></channel-list>
  10 + </el-submenu>
  11 + <el-menu-item v-else :index="item.key" @click="sendDevicePush(item)">
  12 + <template slot="title" >
  13 + <i class="el-icon-switch-button" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i>
  14 + <span slot="title">{{item.title}}</span>
  15 + </template>
  16 + </el-menu-item>
  17 + </div> -->
  18 + <div >
  19 + <template v-if="!item.hasChildren">
  20 + <el-menu-item :index="item.key" @click="sendDevicePush(item)">
  21 + <i class="el-icon-video-camera" :style="{color:item.status==1?'#67C23A':'#F56C6C'}"></i>
  22 + {{item.title}}
  23 + </el-menu-item>
  24 + </template>
  25 +
  26 + <el-submenu v-else :index="item.key">
  27 + <template slot="title" >
  28 + <i class="el-icon-location-outline"></i>
  29 + {{item.title}}
  30 + </template>
  31 +
  32 + <template v-for="child in item.children">
  33 + <channel-item
  34 + v-if="child.hasChildren"
  35 + :item="child"
  36 + :key="child.key"
  37 + @sendDevicePush="sendDevicePush"/>
  38 + <el-menu-item v-else :key="child.key" :index="child.key" @click="sendDevicePush(child)">
  39 + <i class="el-icon-video-camera" :style="{color:child.status==1?'#67C23A':'#F56C6C'}"></i>
  40 + {{child.title}}
  41 + </el-menu-item>
  42 + </template>
  43 + </el-submenu>
  44 + </div>
  45 + </div>
  46 +</template>
  47 +<script>
  48 +export default {
  49 + name:'ChannelItem',
  50 + props:{
  51 + list:Array,
  52 + channelId: String,
  53 + item: {
  54 + type: Object,
  55 + required: true
  56 + }
  57 + },
  58 + data () {
  59 + return {
  60 +
  61 + }
  62 + },
  63 + watch: {
  64 + channelId(val) {
  65 + console.log(val);
  66 + }
  67 + },
  68 + methods: {
  69 + sendDevicePush(c) {
  70 + this.$emit('sendDevicePush',c)
  71 + }
  72 + }
  73 +}
  74 +</script>
... ...
web_src/src/components/jessibuca.vue 0 → 100644
  1 +<template>
  2 + <div :id="'jessibuca'+idx" style="width: 100%; height: 100%">
  3 + <div :id="'container'+idx" ref="container" style="width: 100%; height: 100%; background-color: #000" @dblclick="fullscreenSwich">
  4 + <div class="buttons-box" :id="'buttonsBox'+idx">
  5 + <div class="buttons-box-left">
  6 + <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
  7 + <i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause"></i>
  8 + <i class="iconfont icon-stop jessibuca-btn" @click="destroyButton"></i>
  9 + <i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="jessibuca.mute()"></i>
  10 + <i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="jessibuca.cancelMute()"></i>
  11 + </div>
  12 + <div class="buttons-box-right">
  13 + <span class="jessibuca-btn">{{kBps}} kb/s</span>
  14 +<!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
  15 +<!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
  16 + <i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="screenshot" style="font-size: 1rem !important"></i>
  17 + <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i>
  18 + <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i>
  19 + <i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich"></i>
  20 + </div>
  21 + </div>
  22 +
  23 + </div>
  24 + </div>
  25 +</template>
  26 +
  27 +<script>
  28 +export default {
  29 + name: 'jessibuca',
  30 + data() {
  31 + return {
  32 + jessibuca: null,
  33 + playing: false,
  34 + isNotMute: false,
  35 + quieting: false,
  36 + fullscreen: false,
  37 + loaded: false, // mute
  38 + speed: 0,
  39 + performance: "", // 工作情况
  40 + kBps: 0,
  41 + btnDom: null,
  42 + videoInfo: null,
  43 + volume: 1,
  44 + rotate: 0,
  45 + vod: true, // 点播
  46 + forceNoOffscreen: false,
  47 + };
  48 + },
  49 + props: ['videoUrl', 'error', 'hasAudio', 'height','idx'],
  50 + mounted () {
  51 + window.onerror = (msg) => {
  52 + // console.error(msg)
  53 + };
  54 + let paramUrl = decodeURIComponent(this.$route.params.url)
  55 + this.$nextTick(() =>{
  56 + let dom = document.getElementById("container"+this.idx);
  57 + // dom.style.height = (9/16 ) * dom.clientWidth + "px"
  58 + if (typeof (this.videoUrl) == "undefined") {
  59 + this.videoUrl = paramUrl;
  60 + }
  61 + this.btnDom = document.getElementById("buttonsBox"+this.idx);
  62 + console.log("初始化时的地址为: " + this.videoUrl)
  63 + this.play(this.videoUrl)
  64 + })
  65 + },
  66 + watch:{
  67 + videoUrl(newData, oldData){
  68 + this.play(newData)
  69 + },
  70 + immediate:true
  71 + },
  72 + methods: {
  73 + create(){
  74 + let options = {};
  75 + console.log(this.$refs.container)
  76 + console.log("hasAudio " + !!this.hasAudio)
  77 +
  78 + this.jessibuca = new window.Jessibuca(Object.assign(
  79 + {
  80 + container: this.$refs.container,
  81 + videoBuffer: 0.2, // 最大缓冲时长,单位秒
  82 + isResize: true,
  83 + decoder: "./static/js/jessibuca/index.js",
  84 + // text: "WVP-PRO",
  85 + // background: "bg.jpg",
  86 + loadingText: "加载中",
  87 + hasAudio: !!this.hasAudio,
  88 + debug: false,
  89 + timeout:5,
  90 + supportDblclickFullscreen: false, // 是否支持屏幕的双击事件,触发全屏,取消全屏事件。
  91 + operateBtns: {
  92 + fullscreen: false,
  93 + screenshot: false,
  94 + play: false,
  95 + audio: false,
  96 + },
  97 + record: "record",
  98 + vod: this.vod,
  99 + forceNoOffscreen: this.forceNoOffscreen,
  100 + isNotMute: this.isNotMute,
  101 + },
  102 + options
  103 + ));
  104 +
  105 + let _this = this;
  106 + this.jessibuca.on("load", function () {
  107 + console.log("on load init");
  108 + });
  109 +
  110 + this.jessibuca.on("log", function (msg) {
  111 + console.log("on log", msg);
  112 + });
  113 + this.jessibuca.on("record", function (msg) {
  114 + console.log("on record:", msg);
  115 + });
  116 + this.jessibuca.on("pause", function () {
  117 + _this.playing = false;
  118 + });
  119 + this.jessibuca.on("play", function () {
  120 + _this.playing = true;
  121 + });
  122 + this.jessibuca.on("fullscreen", function (msg) {
  123 + console.log("on fullscreen", msg);
  124 + _this.fullscreen = msg
  125 + });
  126 +
  127 + this.jessibuca.on("mute", function (msg) {
  128 + console.log("on mute", msg);
  129 + _this.isNotMute = !msg;
  130 + });
  131 + this.jessibuca.on("audioInfo", function (msg) {
  132 + // console.log("audioInfo", msg);
  133 + });
  134 +
  135 + this.jessibuca.on("videoInfo", function (msg) {
  136 + this.videoInfo = msg;
  137 + // console.log("videoInfo", msg);
  138 +
  139 + });
  140 +
  141 + this.jessibuca.on("bps", function (bps) {
  142 + // console.log('bps', bps);
  143 +
  144 + });
  145 + let _ts = 0;
  146 + this.jessibuca.on("timeUpdate", function (ts) {
  147 + // console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts);
  148 + _ts = ts;
  149 + });
  150 +
  151 + this.jessibuca.on("videoInfo", function (info) {
  152 + console.log("videoInfo", info);
  153 + });
  154 +
  155 + this.jessibuca.on("error", (error) =>{
  156 + console.log("error", error);
  157 + this.pause()
  158 + });
  159 +
  160 + this.jessibuca.on("timeout", ()=> {
  161 + console.log("timeout");
  162 + // this.pause()
  163 + this.play(this.videoUrl)
  164 + });
  165 +
  166 + this.jessibuca.on('start', function () {
  167 + console.log('start');
  168 + })
  169 +
  170 + this.jessibuca.on("performance", function (performance) {
  171 + let show = "卡顿";
  172 + if (performance === 2) {
  173 + show = "非常流畅";
  174 + } else if (performance === 1) {
  175 + show = "流畅";
  176 + }
  177 + _this.performance = show;
  178 + });
  179 + this.jessibuca.on('buffer', function (buffer) {
  180 + // console.log('buffer', buffer);
  181 + })
  182 +
  183 + this.jessibuca.on('stats', function (stats) {
  184 + // console.log('stats', stats);
  185 + })
  186 +
  187 + this.jessibuca.on('kBps', function (kBps) {
  188 + _this.kBps = Math.round(kBps);
  189 + });
  190 +
  191 + // 显示时间戳 PTS
  192 + this.jessibuca.on('videoFrame', function () {
  193 +
  194 + })
  195 +
  196 + //
  197 + this.jessibuca.on('metadata', function () {
  198 +
  199 + });
  200 + },
  201 + playBtnClick: function (event){
  202 + this.play(this.videoUrl)
  203 + },
  204 + play: function (url) {
  205 + console.log(url)
  206 +
  207 + if (this.jessibuca) {
  208 + this.destroy();
  209 + }
  210 + if(!url){
  211 + return
  212 + }
  213 + this.create();
  214 + this.jessibuca.on("play", () => {
  215 + this.playing = true;
  216 + this.loaded = true;
  217 + this.quieting = this.jessibuca.quieting;
  218 + });
  219 + if (this.jessibuca.hasLoaded()) {
  220 + this.jessibuca.play(url);
  221 + } else {
  222 + this.jessibuca.on("load", () => {
  223 + console.log("load 播放")
  224 + this.jessibuca.play(url);
  225 + });
  226 + }
  227 + },
  228 + pause: function () {
  229 + if (this.jessibuca) {
  230 + this.jessibuca.pause();
  231 + }
  232 + this.playing = false;
  233 + this.err = "";
  234 + this.performance = "";
  235 + },
  236 + destroy: function () {
  237 + if (this.jessibuca) {
  238 + this.jessibuca.destroy();
  239 + }
  240 + if (document.getElementById("buttonsBox"+this.idx) == null) {
  241 + document.getElementById("container"+this.idx).appendChild(this.btnDom)
  242 + }
  243 + this.jessibuca = null;
  244 + this.playing = false;
  245 + this.err = "";
  246 + this.performance = "";
  247 +
  248 + },
  249 + eventcallbacK: function(type, message) {
  250 + // console.log("player 事件回调")
  251 + // console.log(type)
  252 + // console.log(message)
  253 + },
  254 + fullscreenSwich: function (){
  255 + let isFull = this.isFullscreen()
  256 + this.jessibuca.setFullscreen(!isFull)
  257 + this.fullscreen = !isFull;
  258 + },
  259 + isFullscreen: function (){
  260 + return document.fullscreenElement ||
  261 + document.msFullscreenElement ||
  262 + document.mozFullScreenElement ||
  263 + document.webkitFullscreenElement || false;
  264 + },
  265 + resize(){
  266 + this.jessibuca.resize()
  267 + },
  268 + screenshot(){
  269 + this.jessibuca.screenshot('截图','png',0.5)
  270 + // let base64 = this.jessibuca.screenshot("shot","jpeg",0.5,'base64')
  271 + // this.$emit('screenshot',base64)
  272 + },
  273 + destroyButton() {
  274 + this.$emit('destroy', this.idx)
  275 + this.destroy()
  276 + }
  277 + },
  278 + destroyed() {
  279 + if (this.jessibuca) {
  280 + this.jessibuca.destroy();
  281 + }
  282 + this.playing = false;
  283 + this.loaded = false;
  284 + this.performance = "";
  285 + },
  286 +}
  287 +</script>
  288 +
  289 +<style>
  290 + .buttons-box{
  291 + width: 100%;
  292 + height: 28px;
  293 + background-color: rgba(43, 51, 63, 0.7);
  294 + position: absolute;
  295 + display: -webkit-box;
  296 + display: -ms-flexbox;
  297 + display: flex;
  298 + left: 0;
  299 + bottom: 0;
  300 + user-select: none;
  301 + z-index: 10;
  302 + }
  303 + .jessibuca-btn{
  304 + width: 20px;
  305 + color: rgb(255, 255, 255);
  306 + line-height: 27px;
  307 + margin: 0px 10px;
  308 + padding: 0px 2px;
  309 + cursor: pointer;
  310 + text-align: center;
  311 + font-size: 0.8rem !important;
  312 + }
  313 + .buttons-box-right {
  314 + position: absolute;
  315 + right: 0;
  316 + }
  317 +</style>
... ...
web_src/src/components/live.vue 0 → 100644
  1 +<template>
  2 + <div id="devicePosition" style="height: 100%">
  3 + <el-container style="height: 100%">
  4 + <el-header>
  5 + <uiHeader></uiHeader>
  6 + </el-header>
  7 + <el-container v-loading="loading" element-loading-text="拼命加载中">
  8 + <el-aside width="300px" style="background-color: #ffffff">
  9 + <div style="text-align: center;padding-top: 20px;">设备列表</div>
  10 + <el-menu v-loading="loading">
  11 + <el-submenu v-for="device in deviceList" :key="device.deviceId" :index="device.deviceId" @click="sendDevicePush(item)">
  12 + <template slot="title" >
  13 + <i class="el-icon-location-outline"></i>
  14 + {{device.name}}
  15 + </template>
  16 + <ChannelTree :device="device" @sendDevicePush="sendDevicePush"></ChannelTree>
  17 + </el-submenu>
  18 + </el-menu>
  19 + </el-aside>
  20 + <el-container>
  21 + <!-- <LivePlay></LivePlay> -->
  22 + <el-header height="40px" style="text-align: left;font-size: 17px;line-height: 40px;">
  23 + 分屏:
  24 + <i class="el-icon-full-screen btn" :class="{active:spilt==1}" @click="spilt=1"/>
  25 + <i class="el-icon-menu btn" :class="{active:spilt==4}" @click="spilt=4"/>
  26 + <i class="el-icon-s-grid btn" :class="{active:spilt==9}" @click="spilt=9"/>
  27 + </el-header>
  28 + <el-main>
  29 + <div style="width: 100%;height: calc( 100vh - 110px );display: flex;flex-wrap: wrap;background-color: #000;">
  30 + <div v-for="i in spilt" :key="i" class="play-box"
  31 + :style="liveStyle" :class="{redborder:playerIdx == (i-1)}"
  32 + @click="playerIdx = (i-1)"
  33 + >
  34 + <div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 30px;font-weight: bold;">{{i}}</div>
  35 + <player v-else :ref="'player'+i" :videoUrl="videoUrl[i-1]" fluent autoplay :height="true"
  36 + :idx="'player'+i" @screenshot="shot" @destroy="destroy"></player>
  37 + <!-- <player v-else ref="'player'+i" :idx="'player'+i" :visible.sync="showVideoDialog" :videoUrl="videoUrl[i-1]" :height="true" :hasAudio="hasAudio" fluent autoplay live ></player> -->
  38 + </div>
  39 + </div>
  40 + </el-main>
  41 + </el-container>
  42 + </el-container>
  43 + </el-container>
  44 + </div>
  45 +</template>
  46 +
  47 +<script>
  48 + import uiHeader from "./UiHeader.vue";
  49 + import player from './jessibuca.vue'
  50 + import ChannelTree from './channelTree.vue'
  51 +
  52 + export default {
  53 + name: "live",
  54 + components: {
  55 + uiHeader, player, ChannelTree
  56 + },
  57 + data() {
  58 + return {
  59 + showVideoDialog: true,
  60 + hasAudio: false,
  61 + videoUrl:[''],
  62 + spilt:1,//分屏
  63 + playerIdx:0,//激活播放器
  64 +
  65 + deviceList: [], //设备列表
  66 + currentDevice: {}, //当前操作设备对象
  67 +
  68 + videoComponentList: [],
  69 + updateLooper: 0, //数据刷新轮训标志
  70 + currentDeviceChannelsLenth:0,
  71 + winHeight: window.innerHeight - 200,
  72 + currentPage:1,
  73 + count:15,
  74 + total:0,
  75 + getDeviceListLoading: false,
  76 +
  77 + //channel
  78 + searchSrt: "",
  79 + channelType: "",
  80 + online: "",
  81 + channelTotal:0,
  82 + deviceChannelList:[],
  83 + loading:false
  84 + };
  85 + },
  86 + mounted() {
  87 + this.initData();
  88 +
  89 + },
  90 + created(){
  91 + this.checkPlayByParam()
  92 + },
  93 +
  94 + computed:{
  95 + liveStyle(){
  96 + if(this.spilt==1){
  97 + return {width:'100%',height:'100%'}
  98 + }else if(this.spilt==4){
  99 + return {width:'49%',height:'49%'}
  100 + }else if(this.spilt==9){
  101 + return {width:'32%',height:'32%'}
  102 + }
  103 + }
  104 + },
  105 + watch:{
  106 + spilt(newValue){
  107 + console.log("切换画幅;"+newValue)
  108 + let that = this
  109 + for (let i = 1; i <= newValue; i++) {
  110 + if(!that.$refs['player'+i]){
  111 + continue
  112 + }
  113 + this.$nextTick(()=>{
  114 + if(that.$refs['player'+i] instanceof Array){
  115 + that.$refs['player'+i][0].resize()
  116 + }else {
  117 + that.$refs['player'+i].resize()
  118 + }
  119 + })
  120 +
  121 + }
  122 + window.localStorage.setItem('split',newValue)
  123 + },
  124 + '$route.fullPath':'checkPlayByParam'
  125 + },
  126 + destroyed() {
  127 + clearTimeout(this.updateLooper);
  128 + },
  129 + methods: {
  130 + initData: function () {
  131 + this.getDeviceList();
  132 +
  133 + },
  134 + destroy(idx) {
  135 + console.log(idx);
  136 + this.clear(idx.substring(idx.length-1))
  137 + },
  138 + getDeviceList: function() {
  139 + let that = this;
  140 + this.$axios({
  141 + method: 'get',
  142 + url:`/api/device/query/devices`,
  143 + params: {
  144 + page: that.currentPage,
  145 + count: that.count
  146 + }
  147 + }).then(function (res) {
  148 + console.log(res.data.list);
  149 + that.total = res.data.total;
  150 +
  151 + that.deviceList = res.data.list.map(item=>{return {deviceChannelList:[],...item}});
  152 + that.getDeviceListLoading = false;
  153 + }).catch(function (error) {
  154 + console.log(error);
  155 + that.getDeviceListLoading = false;
  156 + });
  157 + },
  158 + //通知设备上传媒体流
  159 + sendDevicePush: function (itemData) {
  160 + if(itemData.status===0){
  161 + this.$message.error('设备离线!');
  162 + return
  163 + }
  164 + this.save(itemData)
  165 + let deviceId = itemData.deviceId;
  166 + // this.isLoging = true;
  167 + let channelId = itemData.channelId;
  168 + console.log("通知设备推流1:" + deviceId + " : " + channelId );
  169 + let idxTmp = this.playerIdx
  170 + let that = this;
  171 + this.loading = true
  172 + this.$axios({
  173 + method: 'get',
  174 + url: '/api/play/start/' + deviceId + '/' + channelId
  175 + }).then(function (res) {
  176 + // that.isLoging = false;
  177 + console.log('=====----=====')
  178 + console.log(res)
  179 + if (res.data.code == 0 && res.data.data) {
  180 + itemData.playUrl = res.data.data.httpsFlv
  181 + that.setPlayUrl(res.data.data.ws_flv,idxTmp)
  182 + }else {
  183 + that.$message.error(res.data.msg);
  184 + }
  185 + }).catch(function (e) {
  186 + }).finally(()=>{
  187 + that.loading = false
  188 + });
  189 + },
  190 + setPlayUrl(url,idx){
  191 + this.$set(this.videoUrl,idx,url)
  192 + let _this = this
  193 + setTimeout(()=>{
  194 + window.localStorage.setItem('videoUrl',JSON.stringify(_this.videoUrl))
  195 + },100)
  196 +
  197 + },
  198 + checkPlayByParam(){
  199 + let {deviceId,channelId} = this.$route.query
  200 + if(deviceId && channelId){
  201 + this.sendDevicePush({deviceId,channelId})
  202 + }
  203 + },
  204 + convertImageToCanvas(image) {
  205 + var canvas = document.createElement("canvas");
  206 + canvas.width = image.width;
  207 + canvas.height = image.height;
  208 + canvas.getContext("2d").drawImage(image, 0, 0);
  209 + return canvas;
  210 + },
  211 + shot(e){
  212 + // console.log(e)
  213 + // send({code:'image',data:e})
  214 + var base64ToBlob = function(code) {
  215 + let parts = code.split(';base64,');
  216 + let contentType = parts[0].split(':')[1];
  217 + let raw = window.atob(parts[1]);
  218 + let rawLength = raw.length;
  219 + let uInt8Array = new Uint8Array(rawLength);
  220 + for(let i = 0; i < rawLength; ++i) {
  221 + uInt8Array[i] = raw.charCodeAt(i);
  222 + }
  223 + return new Blob([uInt8Array], {
  224 + type: contentType
  225 + });
  226 + };
  227 + let aLink = document.createElement('a');
  228 + let blob = base64ToBlob(e); //new Blob([content]);
  229 + let evt = document.createEvent("HTMLEvents");
  230 + evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
  231 + aLink.download = '截图';
  232 + aLink.href = URL.createObjectURL(blob);
  233 + aLink.click();
  234 + },
  235 + save(item){
  236 + let dataStr = window.localStorage.getItem('playData') || '[]'
  237 + let data = JSON.parse(dataStr);
  238 + data[this.playerIdx] = item
  239 + window.localStorage.setItem('playData',JSON.stringify(data))
  240 + },
  241 + clear(idx) {
  242 + let dataStr = window.localStorage.getItem('playData') || '[]'
  243 + let data = JSON.parse(dataStr);
  244 + data[idx-1] = null;
  245 + console.log(data);
  246 + window.localStorage.setItem('playData',JSON.stringify(data))
  247 + },
  248 + loadAndPlay(){
  249 + let dataStr = window.localStorage.getItem('playData') || '[]'
  250 + let data = JSON.parse(dataStr);
  251 +
  252 + data.forEach((item,i)=>{
  253 + if(item){
  254 + this.playerIdx = i
  255 + this.sendDevicePush(item)
  256 + }
  257 + })
  258 + }
  259 + }
  260 + };
  261 +</script>
  262 +<style>
  263 + .btn{
  264 + margin: 0 10px;
  265 +
  266 + }
  267 + .btn:hover{
  268 + color: #409EFF;
  269 + }
  270 + .btn.active{
  271 + color: #409EFF;
  272 +
  273 + }
  274 + .redborder{
  275 + border: 2px solid red !important;
  276 + }
  277 + .play-box{
  278 + background-color: #000000;
  279 + border: 2px solid #505050;
  280 + display: flex;
  281 + align-items: center;
  282 + justify-content: center;
  283 + }
  284 +</style>
  285 +<style>
  286 + .videoList {
  287 + display: flex;
  288 + flex-wrap: wrap;
  289 + align-content: flex-start;
  290 + }
  291 +
  292 + .video-item {
  293 + position: relative;
  294 + width: 15rem;
  295 + height: 10rem;
  296 + margin-right: 1rem;
  297 + background-color: #000000;
  298 + }
  299 +
  300 + .video-item-img {
  301 + position: absolute;
  302 + top: 0;
  303 + bottom: 0;
  304 + left: 0;
  305 + right: 0;
  306 + margin: auto;
  307 + width: 100%;
  308 + height: 100%;
  309 + }
  310 +
  311 + .video-item-img:after {
  312 + content: "";
  313 + display: inline-block;
  314 + position: absolute;
  315 + z-index: 2;
  316 + top: 0;
  317 + bottom: 0;
  318 + left: 0;
  319 + right: 0;
  320 + margin: auto;
  321 + width: 3rem;
  322 + height: 3rem;
  323 + background-image: url("../assets/loading.png");
  324 + background-size: cover;
  325 + background-color: #000000;
  326 + }
  327 +
  328 + .video-item-title {
  329 + position: absolute;
  330 + bottom: 0;
  331 + color: #000000;
  332 + background-color: #ffffff;
  333 + line-height: 1.5rem;
  334 + padding: 0.3rem;
  335 + width: 14.4rem;
  336 + }
  337 +
  338 + .baidumap {
  339 + width: 100%;
  340 + height: 100%;
  341 + border: none;
  342 + position: absolute;
  343 + left: 0;
  344 + top: 0;
  345 + right: 0;
  346 + bottom: 0;
  347 + margin: auto;
  348 + }
  349 +
  350 + /* 去除百度地图版权那行字 和 百度logo */
  351 + .baidumap > .BMap_cpyCtrl {
  352 + display: none !important;
  353 + }
  354 + .baidumap > .anchorBL {
  355 + display: none !important;
  356 + }
  357 +</style>
... ...
web_src/src/router/index.js
... ... @@ -15,6 +15,7 @@ import test from &#39;../components/test.vue&#39;
15 15 import web from '../components/setting/Web.vue'
16 16 import sip from '../components/setting/Sip.vue'
17 17 import media from '../components/setting/Media.vue'
  18 +import live from '../components/live.vue'
18 19  
19 20 import wasmPlayer from '../components/dialog/jessibuca.vue'
20 21 import rtcPlayer from '../components/dialog/rtcPlayer.vue'
... ... @@ -35,6 +36,10 @@ export default new VueRouter({
35 36 component: control,
36 37 },
37 38 {
  39 + path: '/live',
  40 + component: live,
  41 + },
  42 + {
38 43 path: '/deviceList',
39 44 component: deviceList,
40 45 },
... ...