Compare commits

...

9 Commits

Author SHA1 Message Date
阿斌
8bdb311ee3
Pre Merge pull request !36 from 阿斌/N/A 2025-05-23 10:19:07 +00:00
lin
75b1e8deb2 Merge branch '2.7.3'
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
2025-05-23 18:18:32 +08:00
648540858
0df557750f
Merge pull request #1859 from Digital631/master
修复了分配监控的页面自适应
2025-05-23 18:17:41 +08:00
lin
601d2b5d2b 修复集群推流BUG 2025-05-23 18:06:54 +08:00
lin
55f36f660b 修复集群播放BUG 2025-05-23 17:26:17 +08:00
尚鹏暃
1a2de7a1c6 修复了分配监控的页面自适应 2025-05-23 16:13:57 +08:00
尚鹏暃
97c53f07c8 修复了分配监控的页面自适应 2025-05-23 15:59:58 +08:00
lin
d825b986dd 推流列表和拉流代理支持根据来源ip返回流地址 2025-05-23 09:20:22 +08:00
阿斌
da98101aac
update src/main/resources/civilCode.csv.
行政规划错误。江苏南通海门市,修改为海门区,浙江杭州删除下城区、江干区,新增钱塘区,临平区

Signed-off-by: 阿斌 <38912748@qq.com>
2024-12-15 08:58:42 +00:00
31 changed files with 703 additions and 468 deletions

View File

@ -98,7 +98,7 @@ public class SubscribeHolder {
for (Platform platform : platformList) { for (Platform platform : platformList) {
String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platform.getServerGBId()); String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platform.getServerGBId());
if (redisTemplate.hasKey(key)) { if (redisTemplate.hasKey(key)) {
result.add(platform.getServerId()); result.add(platform.getServerGBId());
} }
} }
return result; return result;
@ -112,7 +112,7 @@ public class SubscribeHolder {
for (Platform platform : platformList) { for (Platform platform : platformList) {
String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platform.getServerGBId()); String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platform.getServerGBId());
if (redisTemplate.hasKey(key)) { if (redisTemplate.hasKey(key)) {
result.add(platform.getServerId()); result.add(platform.getServerGBId());
} }
} }
return result; return result;

View File

@ -89,8 +89,8 @@ public interface PlatformMapper {
@Select("SELECT * FROM wvp_platform WHERE id=#{id}") @Select("SELECT * FROM wvp_platform WHERE id=#{id}")
Platform query(int id); Platform query(int id);
@Update("UPDATE wvp_platform SET status=#{online} WHERE id=#{id}" ) @Update("UPDATE wvp_platform SET status=#{online}, server_id = #{serverId} WHERE id=#{id}" )
int updateStatus(@Param("id") int id, @Param("online") boolean online); int updateStatus(@Param("id") int id, @Param("online") boolean online, @Param("serverId") String serverId);
@Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id") @Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id")
List<String> queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId); List<String> queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId);
@ -104,7 +104,7 @@ public interface PlatformMapper {
@Select("SELECT * FROM wvp_platform WHERE enable=true and server_id = #{serverId}") @Select("SELECT * FROM wvp_platform WHERE enable=true and server_id = #{serverId}")
List<Platform> queryServerIdsWithEnableAndServer(@Param("serverId") String serverId); List<Platform> queryServerIdsWithEnableAndServer(@Param("serverId") String serverId);
@Update("UPDATE wvp_platform SET status=false" ) @Update("UPDATE wvp_platform SET status=false where server_id = #{serverId}" )
void offlineAll(); void offlineAll(@Param("serverId") String serverId);
} }

View File

@ -12,6 +12,7 @@ import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -26,6 +27,7 @@ import java.util.Set;
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月6日 上午11:30:50 * @date: 2020年5月6日 上午11:30:50
*/ */
@Slf4j
@Component @Component
public class EventPublisher { public class EventPublisher {
@ -72,12 +74,7 @@ public class EventPublisher {
} }
public void catalogEventPublish(Platform platform, List<CommonGBChannel> deviceChannels, String type, boolean share) { public void catalogEventPublish(Platform platform, List<CommonGBChannel> deviceChannels, String type, boolean share) {
if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) { if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) {
// 指定了上级平台的推送则发送到指定的设备未指定的则全部发送 接收后各自处理自己的 log.info("[国标级联] 目录状态推送, 此上级平台由其他服务处理,消息已经忽略");
CatalogEvent outEvent = new CatalogEvent(this);
outEvent.setChannels(deviceChannels);
outEvent.setType(type);
outEvent.setPlatform(platform);
redisRpcService.catalogEventPublish(platform.getServerId(), outEvent);
return; return;
} }
CatalogEvent outEvent = new CatalogEvent(this); CatalogEvent outEvent = new CatalogEvent(this);
@ -96,12 +93,11 @@ public class EventPublisher {
} }
outEvent.setChannels(channels); outEvent.setChannels(channels);
outEvent.setType(type); outEvent.setType(type);
if (platform != null) {
outEvent.setPlatform(platform); outEvent.setPlatform(platform);
applicationEventPublisher.publishEvent(outEvent);
if (platform == null && share) {
// 如果没指定上级平台则推送消息到所有在线的wvp处理自己含有的平台的目录更新
redisRpcService.catalogEventPublish(null, outEvent);
} }
applicationEventPublisher.publishEvent(outEvent);
} }
public void mobilePositionEventPublish(MobilePosition mobilePosition) { public void mobilePositionEventPublish(MobilePosition mobilePosition) {

View File

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog; package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
@ -39,22 +40,30 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
@Autowired @Autowired
private SubscribeHolder subscribeHolder; private SubscribeHolder subscribeHolder;
@Autowired
private UserSetting userSetting;
@Override @Override
public void onApplicationEvent(CatalogEvent event) { public void onApplicationEvent(CatalogEvent event) {
SubscribeInfo subscribe = null; SubscribeInfo subscribe = null;
Platform parentPlatform = null; Platform parentPlatform = null;
log.info("[Catalog事件: {}] 通道数量: {}", event.getType(), event.getChannels().size());
Map<String, List<Platform>> parentPlatformMap = new HashMap<>(); Map<String, List<Platform>> platformMap = new HashMap<>();
Map<String, CommonGBChannel> channelMap = new HashMap<>(); Map<String, CommonGBChannel> channelMap = new HashMap<>();
if (event.getPlatform() != null) { if (event.getPlatform() != null) {
parentPlatform = event.getPlatform(); parentPlatform = event.getPlatform();
if (parentPlatform.getServerGBId() == null) {
log.info("[Catalog事件: {}] 平台服务国标编码未找到", event.getType());
return;
}
subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()); subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId());
if (subscribe == null) { if (subscribe == null) {
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
return; return;
} }
}else { }else {
List<Platform> allPlatform = platformService.queryAll(); List<Platform> allPlatform = platformService.queryAll(userSetting.getServerId());
// 获取所用订阅 // 获取所用订阅
List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform); List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform);
if (event.getChannels() != null) { if (event.getChannels() != null) {
@ -62,10 +71,14 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
for (CommonGBChannel deviceChannel : event.getChannels()) { for (CommonGBChannel deviceChannel : event.getChannels()) {
List<Platform> parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId( List<Platform> parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId(
deviceChannel.getGbId(), platforms); deviceChannel.getGbId(), platforms);
parentPlatformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB); platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB);
channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel); channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel);
} }
}else {
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
} }
}else {
log.info("[Catalog事件: {}] 事件内通道数为0", event.getType());
} }
} }
switch (event.getType()) { switch (event.getType()) {
@ -74,32 +87,32 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
case CatalogEvent.DEL: case CatalogEvent.DEL:
if (parentPlatform != null) { if (parentPlatform != null) {
List<CommonGBChannel> deviceChannelList = new ArrayList<>(); List<CommonGBChannel> channels = new ArrayList<>();
if (event.getChannels() != null) { if (event.getChannels() != null) {
deviceChannelList.addAll(event.getChannels()); channels.addAll(event.getChannels());
} }
if (!deviceChannelList.isEmpty()) { if (!channels.isEmpty()) {
log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), deviceChannelList.size()); log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), channels.size());
try { try {
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null); sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, channels, subscribe, null);
} catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
IllegalAccessException e) { IllegalAccessException e) {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
} }
} }
}else if (!parentPlatformMap.keySet().isEmpty()) { }else if (!platformMap.keySet().isEmpty()) {
for (String gbId : parentPlatformMap.keySet()) { for (String serverGbId : platformMap.keySet()) {
List<Platform> parentPlatforms = parentPlatformMap.get(gbId); List<Platform> platformList = platformMap.get(serverGbId);
if (parentPlatforms != null && !parentPlatforms.isEmpty()) { if (platformList != null && !platformList.isEmpty()) {
for (Platform platform : parentPlatforms) { for (Platform platform : platformList) {
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
if (subscribeInfo == null) { if (subscribeInfo == null) {
continue; continue;
} }
log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId); log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), serverGbId);
List<CommonGBChannel> deviceChannelList = new ArrayList<>(); List<CommonGBChannel> deviceChannelList = new ArrayList<>();
CommonGBChannel deviceChannel = new CommonGBChannel(); CommonGBChannel deviceChannel = new CommonGBChannel();
deviceChannel.setGbDeviceId(gbId); deviceChannel.setGbDeviceId(serverGbId);
deviceChannelList.add(deviceChannel); deviceChannelList.add(deviceChannel);
try { try {
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null); sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
@ -108,6 +121,8 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
} }
} }
}else {
log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getType(), serverGbId);
} }
} }
} }
@ -132,9 +147,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
} }
} }
}else if (!parentPlatformMap.keySet().isEmpty()) { }else if (!platformMap.keySet().isEmpty()) {
for (String gbId : parentPlatformMap.keySet()) { for (String gbId : platformMap.keySet()) {
List<Platform> parentPlatforms = parentPlatformMap.get(gbId); List<Platform> parentPlatforms = platformMap.get(gbId);
if (parentPlatforms != null && !parentPlatforms.isEmpty()) { if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
for (Platform platform : parentPlatforms) { for (Platform platform : parentPlatforms) {
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());

View File

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition; package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
@ -37,12 +38,15 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
@Autowired @Autowired
private SubscribeHolder subscribeHolder; private SubscribeHolder subscribeHolder;
@Autowired
private UserSetting userSetting;
@Override @Override
public void onApplicationEvent(MobilePositionEvent event) { public void onApplicationEvent(MobilePositionEvent event) {
if (event.getMobilePosition().getChannelId() == 0) { if (event.getMobilePosition().getChannelId() == 0) {
return; return;
} }
List<Platform> allPlatforms = platformService.queryAll(); List<Platform> allPlatforms = platformService.queryAll(userSetting.getServerId());
// 获取所用订阅 // 获取所用订阅
List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms); List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms);
if (platforms.isEmpty()) { if (platforms.isEmpty()) {

View File

@ -80,6 +80,6 @@ public interface IPlatformService {
void delete(Integer platformId, CommonCallback<Object> callback); void delete(Integer platformId, CommonCallback<Object> callback);
List<Platform> queryAll(); List<Platform> queryAll(String serverId);
} }

View File

@ -192,6 +192,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
} }
sync(device); sync(device);
}else { }else {
device.setServerId(userSetting.getServerId());
if(!device.isOnLine()){ if(!device.isOnLine()){
device.setOnLine(true); device.setOnLine(true);
device.setCreateTime(now); device.setCreateTime(now);
@ -307,15 +308,15 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
return; return;
} }
for (Device device : deviceList) { for (Device device : deviceList) {
if (device == null || !device.isOnLine()) { if (device == null || !device.isOnLine() || !device.getServerId().equals(userSetting.getServerId())) {
continue; continue;
} }
if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
log.info("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); log.debug("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
addCatalogSubscribe(device, null); addCatalogSubscribe(device, null);
} }
if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
log.info("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); log.debug("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
addMobilePositionSubscribe(device, null); addMobilePositionSubscribe(device, null);
} }
} }

View File

@ -160,30 +160,26 @@ public class GbChannelServiceImpl implements IGbChannelService {
log.warn("[多个通道离线] 通道数量为0更新失败"); log.warn("[多个通道离线] 通道数量为0更新失败");
return 0; return 0;
} }
List<CommonGBChannel> onlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "ON"); log.info("[通道离线] 共 {} 个", commonGBChannelList.size());
if (onlineChannelList.isEmpty()) {
log.info("[多个通道离线] 更新失败, 参数内通道已经离线, 无需更新");
return 0;
}
int limitCount = 1000; int limitCount = 1000;
int result = 0; int result = 0;
if (onlineChannelList.size() > limitCount) { if (commonGBChannelList.size() > limitCount) {
for (int i = 0; i < onlineChannelList.size(); i += limitCount) { for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
int toIndex = i + limitCount; int toIndex = i + limitCount;
if (i + limitCount > onlineChannelList.size()) { if (i + limitCount > commonGBChannelList.size()) {
toIndex = onlineChannelList.size(); toIndex = commonGBChannelList.size();
} }
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList.subList(i, toIndex), "OFF"); result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF");
} }
} else { } else {
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList, "OFF"); result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF");
} }
if (result > 0) { if (result > 0) {
try { try {
// 发送catalog // 发送catalog
eventPublisher.catalogEventPublish(null, onlineChannelList, CatalogEvent.OFF); eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.OFF);
} catch (Exception e) { } catch (Exception e) {
log.warn("[多个通道离线] 发送失败,数量:{}", onlineChannelList.size(), e); log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e);
} }
} }
return result; return result;
@ -214,32 +210,25 @@ public class GbChannelServiceImpl implements IGbChannelService {
log.warn("[多个通道上线] 通道数量为0更新失败"); log.warn("[多个通道上线] 通道数量为0更新失败");
return 0; return 0;
} }
List<CommonGBChannel> offlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "OFF");
if (offlineChannelList.isEmpty()) {
log.warn("[多个通道上线] 更新失败, 参数内通道已经上线");
return 0;
}
// 批量更新 // 批量更新
int limitCount = 1000; int limitCount = 1000;
int result = 0; int result = 0;
if (offlineChannelList.size() > limitCount) { if (commonGBChannelList.size() > limitCount) {
for (int i = 0; i < offlineChannelList.size(); i += limitCount) { for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
int toIndex = i + limitCount; int toIndex = i + limitCount;
if (i + limitCount > offlineChannelList.size()) { if (i + limitCount > commonGBChannelList.size()) {
toIndex = offlineChannelList.size(); toIndex = commonGBChannelList.size();
} }
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList.subList(i, toIndex), "ON"); result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON");
} }
} else { } else {
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList, "ON"); result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON");
} }
if (result > 0) {
try { try {
// 发送catalog // 发送catalog
eventPublisher.catalogEventPublish(null, offlineChannelList, CatalogEvent.ON); eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.ON);
} catch (Exception e) { } catch (Exception e) {
log.warn("[多个通道上线] 发送失败,数量:{}", offlineChannelList.size(), e); log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e);
}
} }
return result; return result;

View File

@ -63,11 +63,6 @@ import java.util.concurrent.TimeUnit;
@Order(value=15) @Order(value=15)
public class PlatformServiceImpl implements IPlatformService, CommandLineRunner { public class PlatformServiceImpl implements IPlatformService, CommandLineRunner {
private final static String REGISTER_KEY_PREFIX = "platform_register_";
private final static String REGISTER_FAIL_AGAIN_KEY_PREFIX = "platform_register_fail_again_";
private final static String KEEPALIVE_KEY_PREFIX = "platform_keepalive_";
@Autowired @Autowired
private PlatformMapper platformMapper; private PlatformMapper platformMapper;
@ -133,7 +128,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
sendUnRegister(platform, taskInfo.getSipTransactionInfo()); sendUnRegister(platform, taskInfo.getSipTransactionInfo());
} }
// 启动时所有平台默认离线 // 启动时所有平台默认离线
platformMapper.offlineAll(); platformMapper.offlineAll(userSetting.getServerId());
} }
@Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 @Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次
public void statusLostCheck(){ public void statusLostCheck(){
@ -199,6 +194,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
return; return;
} }
log.info("[集群] 检测到 {} 已离线", serverId); log.info("[集群] 检测到 {} 已离线", serverId);
redisCatchStorage.removeOfflineWVPInfo(serverId);
String chooseServerId = redisCatchStorage.chooseOneServer(serverId); String chooseServerId = redisCatchStorage.chooseOneServer(serverId);
if (!userSetting.getServerId().equals(chooseServerId)){ if (!userSetting.getServerId().equals(chooseServerId)){
return; return;
@ -390,8 +386,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
this::keepaliveExpire); this::keepaliveExpire);
statusTaskRunner.addKeepAliveTask(keepaliveTask); statusTaskRunner.addKeepAliveTask(keepaliveTask);
platformMapper.updateStatus(platform.getId(), true, userSetting.getServerId());
platformMapper.updateStatus(platform.getId(), true);
if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) { if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) {
if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) { if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) {
@ -481,7 +476,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
platformMapper.updateStatus(platform.getId(), false); platformMapper.updateStatus(platform.getId(), false, userSetting.getServerId());
// 停止所有推流 // 停止所有推流
log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId()); log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId());
@ -521,7 +516,6 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
gpsMsgInfo = null; gpsMsgInfo = null;
} }
if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){ if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){
gpsMsgInfo = new GPSMsgInfo(); gpsMsgInfo = new GPSMsgInfo();
gpsMsgInfo.setId(channel.getGbDeviceId()); gpsMsgInfo.setId(channel.getGbDeviceId());
@ -870,7 +864,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
} }
@Override @Override
public List<Platform> queryAll() { public List<Platform> queryAll(String serverId) {
return platformMapper.queryAll(); return platformMapper.queryByServerId(serverId);
} }
} }

View File

@ -340,7 +340,7 @@ public class PlayServiceImpl implements IPlayService {
InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId());
if (inviteInfoInCatch != null ) { if (inviteInfoInCatch != null ) {
if (inviteInfoInCatch.getStreamInfo() == null) { if (inviteInfoInCatch.getStreamInfo() == null) {
// 释放生成的ssrc使用上一次申请的322 // 释放生成的ssrc使用上一次申请的
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
// 点播发起了但是尚未成功, 仅注册回调等待结果即可 // 点播发起了但是尚未成功, 仅注册回调等待结果即可

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.session;
import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -13,6 +14,7 @@ import java.util.Set;
/** /**
* ssrc使用 * ssrc使用
*/ */
@Slf4j
@Component @Component
public class SSRCFactory { public class SSRCFactory {
@ -93,6 +95,7 @@ public class SSRCFactory {
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
Long size = redisTemplate.opsForSet().size(redisKey); Long size = redisTemplate.opsForSet().size(redisKey);
if (size == null || size == 0) { if (size == null || size == 0) {
log.info("[获取 SSRC 失败] redisKey {}", redisKey);
throw new RuntimeException("ssrc已经用完"); throw new RuntimeException("ssrc已经用完");
} else { } else {
// 在集合中移除并返回一个随机成员 // 在集合中移除并返回一个随机成员

View File

@ -143,6 +143,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc()); mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc());
} }
} }
if (sendRtpItem.getServerId().equals(userSetting.getServerId())) {
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaServer != null) { if (mediaServer != null) {
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId()); AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId());
@ -154,7 +155,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId); MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId);
if (mediaInfo.getReaderCount() <= 0) { if (mediaInfo != null && mediaInfo.getReaderCount() <= 0) {
log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId()); Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId());
@ -177,6 +178,10 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
} }
} }
} }
} else {
// TODO 流再其他wvp上时应该通知这个wvp停止推流和发送BYE
}
} }
// 可能是设备发送的停止 // 可能是设备发送的停止
SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callIdHeader.getCallId()); SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByCallId(callIdHeader.getCallId());

View File

@ -172,11 +172,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 点播成功 TODO 可以在此处检测cancel命令是否存在存在则不发送 // 点播成功 TODO 可以在此处检测cancel命令是否存在存在则不发送
if (userSetting.getUseCustomSsrcForParentInvite()) { if (userSetting.getUseCustomSsrcForParentInvite()) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式 // 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
MediaServer mediaServer = mediaServerService.getOne(streamInfo.getMediaServer().getId());
if (mediaServer != null) {
String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName()) String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName())
? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId()) ? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId())
: ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId()); : ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId());
inviteInfo.setSsrc(ssrc); inviteInfo.setSsrc(ssrc);
} }
}
// 构建sendRTP内容 // 构建sendRTP内容
SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(), SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(),
inviteInfo.getIp(), inviteInfo.getPort(), inviteInfo.getSsrc(), platform.getServerGBId(), inviteInfo.getIp(), inviteInfo.getPort(), inviteInfo.getSsrc(), platform.getServerGBId(),

View File

@ -26,7 +26,7 @@ public interface IRedisRpcPlayService {
String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2); String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2);
void playPush(Integer id, ErrorCallback<StreamInfo> callback); void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback);
StreamInfo playProxy(String serverId, int id); StreamInfo playProxy(String serverId, int id);

View File

@ -93,11 +93,11 @@ public class RedisAlarmMsgListener implements MessageListener {
log.warn("[REDIS的ALARM通知]消息解析失败"); log.warn("[REDIS的ALARM通知]消息解析失败");
continue; continue;
} }
String gbId = alarmChannelMessage.getGbId(); String chanelId = alarmChannelMessage.getGbId();
DeviceAlarm deviceAlarm = new DeviceAlarm(); DeviceAlarm deviceAlarm = new DeviceAlarm();
deviceAlarm.setCreateTime(DateUtil.getNow()); deviceAlarm.setCreateTime(DateUtil.getNow());
deviceAlarm.setChannelId(gbId); deviceAlarm.setChannelId(chanelId);
deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription()); deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn()); deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType()); deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
@ -106,7 +106,7 @@ public class RedisAlarmMsgListener implements MessageListener {
deviceAlarm.setLongitude(0); deviceAlarm.setLongitude(0);
deviceAlarm.setLatitude(0); deviceAlarm.setLatitude(0);
if (ObjectUtils.isEmpty(gbId)) { if (ObjectUtils.isEmpty(chanelId)) {
if (userSetting.getSendToPlatformsWhenIdLost()) { if (userSetting.getSendToPlatformsWhenIdLost()) {
// 发送给所有的上级 // 发送给所有的上级
List<Platform> parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId()); List<Platform> parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId());
@ -148,24 +148,26 @@ public class RedisAlarmMsgListener implements MessageListener {
} }
} else { } else {
// 获取该通道ID是属于设备还是对应的上级平台 // 获取该通道ID是属于设备还是对应的上级平台
Device device = deviceService.getDeviceBySourceChannelDeviceId(gbId); Device device = deviceService.getDeviceBySourceChannelDeviceId(chanelId);
List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(gbId); List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(chanelId);
if (device != null && (platforms == null || platforms.isEmpty())) { if (device != null && device.getServerId().equals(userSetting.getServerId()) && (platforms == null || platforms.isEmpty())) {
try { try {
commander.sendAlarmMessage(device, deviceAlarm); commander.sendAlarmMessage(device, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage()); log.error("[命令发送失败] 发送报警: {}", e.getMessage());
} }
} else if (device == null && (platforms != null && !platforms.isEmpty())) { } else if (device == null && (platforms != null && !platforms.isEmpty() )) {
for (Platform platform : platforms) { for (Platform platform : platforms) {
if (platform.getServerId().equals(userSetting.getServerId())) {
try { try {
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage()); log.error("[命令发送失败] 发送报警: {}", e.getMessage());
} }
} }
}
} else { } else {
log.warn("[REDIS的ALARM通知] 未查询到" + gbId + "所属的平台或设备"); log.warn("[REDIS的ALARM通知] 未查询到" + chanelId + "所属的平台或设备");
} }
} }
} catch (Exception e) { } catch (Exception e) {

View File

@ -48,7 +48,7 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
@Override @Override
public void onMessage(Message message, byte[] bytes) { public void onMessage(Message message, byte[] bytes) {
log.info("[REDIS: 流设备状态变化] {}", new String(message.getBody())); log.info("[REDIS: 流设备状态变化] {}", new String(message.getBody()));
taskQueue.offer(message); taskQueue.offer(message);
} }
@ -84,11 +84,13 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
if (streamStatusMessage.getOfflineStreams() != null if (streamStatusMessage.getOfflineStreams() != null
&& !streamStatusMessage.getOfflineStreams().isEmpty()) { && !streamStatusMessage.getOfflineStreams().isEmpty()) {
// 更新部分设备离线 // 更新部分设备离线
log.info("[REDIS: 推流设备状态变化] 更新部分设备离线: {}个", streamStatusMessage.getOfflineStreams().size());
streamPushService.offline(streamStatusMessage.getOfflineStreams()); streamPushService.offline(streamStatusMessage.getOfflineStreams());
} }
if (streamStatusMessage.getOnlineStreams() != null && if (streamStatusMessage.getOnlineStreams() != null &&
!streamStatusMessage.getOnlineStreams().isEmpty()) { !streamStatusMessage.getOnlineStreams().isEmpty()) {
// 更新部分设备上线 // 更新部分设备上线
log.info("[REDIS: 推流设备状态变化] 更新部分设备上线: {}个", streamStatusMessage.getOnlineStreams().size());
streamPushService.online(streamStatusMessage.getOnlineStreams()); streamPushService.online(streamStatusMessage.getOnlineStreams());
} }
} catch (Exception e) { } catch (Exception e) {

View File

@ -181,7 +181,8 @@ public class RedisRpcStreamPushController extends RpcController {
*/ */
@RedisRpcMapping("play") @RedisRpcMapping("play")
public RedisRpcResponse play(RedisRpcRequest request) { public RedisRpcResponse play(RedisRpcRequest request) {
int id = Integer.parseInt(request.getParam().toString()); JSONObject paramJson = JSONObject.parseObject(request.getParam().toString());
int id = paramJson.getInteger("id");
RedisRpcResponse response = request.getResponse(); RedisRpcResponse response = request.getResponse();
if (id <= 0) { if (id <= 0) {
response.setStatusCode(ErrorCode.ERROR400.getCode()); response.setStatusCode(ErrorCode.ERROR400.getCode());

View File

@ -193,8 +193,11 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService {
} }
@Override @Override
public void playPush(Integer id, ErrorCallback<StreamInfo> callback) { public void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback) {
RedisRpcRequest request = buildRequest("streamPush/play", id); JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
RedisRpcRequest request = buildRequest("streamPush/play", jsonObject);
request.setToId(serverId);
RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS);
if (response == null) { if (response == null) {
callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null);

View File

@ -27,6 +27,8 @@ public interface IRedisCatchStorage {
*/ */
void updateWVPInfo(ServerInfo serverInfo, int time); void updateWVPInfo(ServerInfo serverInfo, int time);
void removeOfflineWVPInfo(String serverId);
/** /**
* 发送推流生成与推流消失消息 * 发送推流生成与推流消失消息
* @param jsonObject 消息内容 * @param jsonObject 消息内容

View File

@ -84,6 +84,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
redisTemplate.opsForZSet().add(setKey, userSetting.getServerId(), System.currentTimeMillis()); redisTemplate.opsForZSet().add(setKey, userSetting.getServerId(), System.currentTimeMillis());
} }
@Override
public void removeOfflineWVPInfo(String serverId) {
String setKey = VideoManagerConstants.WVP_SERVER_LIST;
// 首次设置就设置为0, 后续值越小说明越是最近启动的
redisTemplate.opsForZSet().remove(setKey, serverId);
}
@Override @Override
public void sendStreamChangeMsg(String type, JSONObject jsonObject) { public void sendStreamChangeMsg(String type, JSONObject jsonObject) {
String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type; String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type;

View File

@ -23,6 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map; import java.util.Map;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -190,12 +193,23 @@ public class StreamProxyController {
@ResponseBody @ResponseBody
@Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "id", description = "代理Id", required = true) @Parameter(name = "id", description = "代理Id", required = true)
public StreamContent start(int id){ public StreamContent start(HttpServletRequest request, int id){
log.info("播放代理: {}", id); log.info("播放代理: {}", id);
StreamInfo streamInfo = streamProxyPlayService.start(id, null, null); StreamInfo streamInfo = streamProxyPlayService.start(id, null, null);
if (streamInfo == null) { if (streamInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
}else { }else {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
return new StreamContent(streamInfo); return new StreamContent(streamInfo);
} }
} }

View File

@ -35,8 +35,11 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -245,7 +248,7 @@ public class StreamPushController {
@GetMapping(value = "/start") @GetMapping(value = "/start")
@ResponseBody @ResponseBody
@Operation(summary = "开始播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "开始播放", security = @SecurityRequirement(name = JwtUtils.HEADER))
public DeferredResult<WVPResult<StreamContent>> start(Integer id){ public DeferredResult<WVPResult<StreamContent>> start(HttpServletRequest request, Integer id){
Assert.notNull(id, "推流ID不可为NULL"); Assert.notNull(id, "推流ID不可为NULL");
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
result.onTimeout(()->{ result.onTimeout(()->{
@ -254,6 +257,17 @@ public class StreamPushController {
}); });
streamPushPlayService.start(id, (code, msg, streamInfo) -> { streamPushPlayService.start(id, (code, msg, streamInfo) -> {
if (code == 0 && streamInfo != null) { if (code == 0 && streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
WVPResult<StreamContent> success = WVPResult.success(new StreamContent(streamInfo)); WVPResult<StreamContent> success = WVPResult.success(new StreamContent(streamInfo));
result.setResult(success); result.setResult(success);
} }

View File

@ -91,7 +91,7 @@ public interface StreamPushMapper {
"(#{item.app}, #{item.stream}) " + "(#{item.app}, #{item.stream}) " +
"</foreach>" + "</foreach>" +
")</script>") ")</script>")
List<StreamPush> getListFromRedis(List<StreamPushItemFromRedis> offlineStreams); List<StreamPush> getListInList(List<StreamPushItemFromRedis> offlineStreams);
@Select("SELECT CONCAT(app,stream) from wvp_stream_push") @Select("SELECT CONCAT(app,stream) from wvp_stream_push")

View File

@ -58,7 +58,7 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService {
Assert.notNull(streamPush, "推流信息未找到"); Assert.notNull(streamPush, "推流信息未找到");
if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) { if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) {
redisRpcPlayService.playPush(id, callback); redisRpcPlayService.playPush(streamPush.getServerId(), id, callback);
return; return;
} }

View File

@ -458,16 +458,27 @@ public class StreamPushServiceImpl implements IStreamPushService {
@Override @Override
public void offline(List<StreamPushItemFromRedis> offlineStreams) { public void offline(List<StreamPushItemFromRedis> offlineStreams) {
// 更新部分设备离线 // 更新部分设备离线
List<StreamPush> streamPushList = streamPushMapper.getListFromRedis(offlineStreams); List<StreamPush> streamPushList = streamPushMapper.getListInList(offlineStreams);
if (streamPushList.isEmpty()) {
log.info("[推流设备] 设备离线操作未发现可操作数据。");
return;
}
List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList);
gbChannelService.offline(commonGBChannelList); gbChannelService.offline(commonGBChannelList);
} }
@Override @Override
public void online(List<StreamPushItemFromRedis> onlineStreams) { public void online(List<StreamPushItemFromRedis> onlineStreams) {
if (onlineStreams.isEmpty()) {
log.info("[设备上线] 推流设备列表为空");
return;
}
// 更新部分设备上线streamPushService // 更新部分设备上线streamPushService
List<StreamPush> streamPushList = streamPushMapper.getListFromRedis(onlineStreams); List<StreamPush> streamPushList = streamPushMapper.getListInList(onlineStreams);
if (streamPushList.isEmpty()) { if (streamPushList.isEmpty()) {
for (StreamPushItemFromRedis onlineStream : onlineStreams) {
log.info("[设备上线] 未查询到这些通道: {}/{}", onlineStream.getApp(), onlineStream.getStream());
}
return; return;
} }
List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList);

View File

@ -861,7 +861,7 @@
320623,如东县,3206 320623,如东县,3206
320681,启东市,3206 320681,启东市,3206
320682,如皋市,3206 320682,如皋市,3206
320684,海门,3206 320684,海门,3206
320685,海安市,3206 320685,海安市,3206
3207,连云港市,32 3207,连云港市,32
320703,连云区,3207 320703,连云区,3207
@ -918,8 +918,6 @@
33,浙江省, 33,浙江省,
3301,杭州市,33 3301,杭州市,33
330102,上城区,3301 330102,上城区,3301
330103,下城区,3301
330104,江干区,3301
330105,拱墅区,3301 330105,拱墅区,3301
330106,西湖区,3301 330106,西湖区,3301
330108,滨江区,3301 330108,滨江区,3301
@ -927,6 +925,8 @@
330110,余杭区,3301 330110,余杭区,3301
330111,富阳区,3301 330111,富阳区,3301
330112,临安区,3301 330112,临安区,3301
330113,临平区,3301
330114,钱塘区,3301
330122,桐庐县,3301 330122,桐庐县,3301
330127,淳安县,3301 330127,淳安县,3301
330182,建德市,3301 330182,建德市,3301

1 编号 名称 上级
861 320623 如东县 3206
862 320681 启东市 3206
863 320682 如皋市 3206
864 320684 海门市 海门区 3206
865 320685 海安市 3206
866 3207 连云港市 32
867 320703 连云区 3207
918 33 浙江省
919 3301 杭州市 33
920 330102 上城区 3301
330103 下城区 3301
330104 江干区 3301
921 330105 拱墅区 3301
922 330106 西湖区 3301
923 330108 滨江区 3301
925 330110 余杭区 3301
926 330111 富阳区 3301
927 330112 临安区 3301
928 330113 临平区 3301
929 330114 钱塘区 3301
930 330122 桐庐县 3301
931 330127 淳安县 3301
932 330182 建德市 3301

View File

@ -1,198 +1,48 @@
module.exports = { module.exports = {
root: true, root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: { env: {
browser: true,
node: true, node: true,
es6: true, browser: true,
},
extends: ["plugin:vue/essential", "eslint:recommended"],
parserOptions: {
parser: "babel-eslint",
}, },
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: { rules: {
"vue/max-attributes-per-line": [2, { // Disable or downgrade problematic rules
"singleline": 10, "vue/require-prop-types": "off",
"multiline": { "vue/require-default-prop": "off",
"max": 1, "vue/no-unused-vars": "warn",
"allowFirstLine": false "no-unused-vars": "warn",
} "no-undef": "warn",
}], eqeqeq: "warn",
"vue/singleline-html-element-content-newline": "off", "no-return-assign": "warn",
"vue/multiline-html-element-content-newline":"off", "new-cap": "warn",
"vue/name-property-casing": ["error", "PascalCase"], "vue/html-self-closing": "off",
"vue/no-v-html": "off", "vue/html-closing-bracket-spacing": "off",
'accessor-pairs': 2, "vue/this-in-template": "off",
'arrow-spacing': [2, { "vue/require-v-for-key": "warn",
'before': true, "vue/valid-v-model": "warn",
'after': true "vue/attributes-order": "off",
}], "no-multiple-empty-lines": "warn",
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', { // Style rules - make them warnings instead of errors
'allowSingleLine': true quotes: ["warn", "single"],
}], "comma-dangle": ["warn", "never"],
'camelcase': [0, { "space-in-parens": "warn",
'properties': 'always' "comma-spacing": "warn",
}], "object-curly-spacing": "warn",
'comma-dangle': [2, 'never'], "arrow-spacing": "warn",
'comma-spacing': [2, { semi: ["warn", "never"],
'before': false, "no-multi-spaces": "warn",
'after': true
}], // Turn off console warnings for development
'comma-style': [2, 'last'], "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
'constructor-super': 2, "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
'curly': [2, 'multi-line'], },
'dot-location': [2, 'property'], globals: {
'eol-last': 2, // Define global variables to prevent 'undefined' errors
'eqeqeq': ["error", "always", {"null": "ignore"}], ZLMRTCClient: "readonly",
'generator-star-spacing': [2, { jessibuca: "readonly",
'before': true, },
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
} }

View File

@ -1,8 +1,8 @@
<template> <template>
<div id="DeviceTree" style="width: 100%;height: 100%; background-color: #FFFFFF; overflow: auto; padding: 30px"> <div id="DeviceTree" class="device-tree-container">
<div style="height: 30px; display: grid; grid-template-columns: auto auto"> <div class="device-tree-header">
<div>通道列表</div> <div class="header-title">通道列表</div>
<div> <div class="header-switch">
<el-switch <el-switch
v-model="showRegion" v-model="showRegion"
active-color="#13ce66" active-color="#13ce66"
@ -12,9 +12,27 @@
/> />
</div> </div>
</div> </div>
<div> <div class="tree-content">
<RegionTree v-if="showRegion" ref="regionTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" /> <div class="tree-wrapper">
<GroupTree v-if="!showRegion" ref="groupTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" /> <RegionTree
v-if="showRegion"
ref="regionTree"
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
<GroupTree
v-if="!showRegion"
ref="groupTree"
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -26,7 +44,24 @@ import GroupTree from './GroupTree.vue'
export default { export default {
name: 'DeviceTree', name: 'DeviceTree',
components: { GroupTree, RegionTree }, components: { GroupTree, RegionTree },
props: ['device', 'onlyCatalog', 'clickEvent', 'contextMenuEvent'], props: {
device: {
type: Object,
default: () => ({})
},
onlyCatalog: {
type: Boolean,
default: false
},
clickEvent: {
type: Function,
default: null
},
contextMenuEvent: {
type: Function,
default: null
}
},
data() { data() {
return { return {
showRegion: true, showRegion: true,
@ -37,15 +72,67 @@ export default {
} }
} }
}, },
destroyed() { mounted() {
// if (this.jessibuca) { // Apply fix for Element UI tree scrollbars after component is mounted
// this.jessibuca.destroy(); this.$nextTick(() => {
// } this.fixTreeScrollbars()
// this.playing = false; this.adjustTreeHeight()
// this.loaded = false;
// this.performance = ""; // Add resize event listener to handle window resizing
window.addEventListener('resize', this.adjustTreeHeight)
})
},
updated() {
// Re-apply fix when component updates (e.g., when switching between RegionTree and GroupTree)
this.$nextTick(() => {
this.fixTreeScrollbars()
this.adjustTreeHeight()
})
},
beforeDestroy() {
// Remove event listener when component is destroyed
window.removeEventListener('resize', this.adjustTreeHeight)
}, },
methods: { methods: {
adjustTreeHeight() {
// Get the container height
const containerHeight = this.$el.clientHeight
// Get the header height
const headerHeight = this.$el.querySelector('.device-tree-header').clientHeight
// Calculate available height for tree
const availableHeight = containerHeight - headerHeight - 30 // 30px for padding
// Set the tree content height
const treeContent = this.$el.querySelector('.tree-content')
if (treeContent) {
treeContent.style.height = `${availableHeight}px`
}
// Ensure tree components adapt to the available height
const treeComponents = this.$el.querySelectorAll('.el-tree')
treeComponents.forEach(tree => {
tree.style.height = '100%'
tree.style.maxHeight = '100%'
})
},
fixTreeScrollbars() {
// Find all el-tree elements within this component and fix their scrolling behavior
const trees = this.$el.querySelectorAll('.el-tree')
trees.forEach(tree => {
tree.style.overflow = 'visible'
tree.style.width = '100%'
// Also fix any scrollable containers within the tree
const scrollContainers = tree.querySelectorAll('[style*="overflow"]')
scrollContainers.forEach(container => {
if (container.style.overflow === 'auto' || container.style.overflow === 'scroll') {
container.style.overflow = 'visible'
}
})
})
},
handleClick: function(tab, event) { handleClick: function(tab, event) {
}, },
treeNodeClickEvent: function(data) { treeNodeClickEvent: function(data) {
@ -62,13 +149,130 @@ export default {
</script> </script>
<style> <style>
.device-tree-main-box{ .device-tree-container {
width: 100%;
height: 100%;
background-color: #FFFFFF;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 15px;
overflow: hidden !important; /* Force no overflow on container */
}
.device-tree-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
min-height: 30px;
}
.header-title {
font-size: 16px;
font-weight: 500;
}
.tree-content {
flex: 1;
overflow: hidden !important;
width: 100%;
margin: 0;
padding: 0;
position: relative;
}
.tree-wrapper {
width: 100%;
height: 100%;
min-width: 0; /* Prevent flex items from overflowing */
position: relative;
}
/* Global fixes for Element UI tree components */
.el-tree {
overflow: visible !important;
width: 100% !important;
min-width: 0 !important;
height: 100% !important;
}
.el-tree-node {
width: 100% !important;
min-width: 0 !important;
}
.el-tree-node__content {
width: 100% !important;
min-width: 0 !important;
}
.el-tree-node__label {
word-break: break-word !important;
white-space: normal !important;
}
/* Fix for any scrollable containers */
[style*="overflow: auto"],
[style*="overflow:auto"],
[style*="overflow: scroll"],
[style*="overflow:scroll"] {
overflow: visible !important;
}
/* Make sure tree nodes are fully visible */
.el-tree-node__children {
overflow: visible !important;
}
/* Ensure tree nodes can be expanded/collapsed */
.el-tree-node__expand-icon {
cursor: pointer;
}
.device-tree-main-box {
text-align: left; text-align: left;
} }
.device-online{
.device-online {
color: #252525; color: #252525;
} }
.device-offline{
.device-offline {
color: #727272; color: #727272;
} }
/* Responsive adjustments */
@media (max-width: 768px) {
.device-tree-container {
padding: 10px;
}
.device-tree-header {
flex-direction: column;
align-items: flex-start;
}
.header-switch {
width: 100%;
margin-top: 5px;
}
}
@media (max-width: 480px) {
.device-tree-container {
padding: 8px;
}
.header-title {
font-size: 14px;
}
/* Adjust el-switch text size for mobile */
.el-switch__label {
font-size: 12px;
}
}
</style> </style>

View File

@ -1,25 +1,26 @@
<template> <template>
<div id="live" style="height: calc(100vh - 124px)"> <div id="live" class="live-container">
<div v-loading="loading" style="height: 100%; display: grid; grid-template-columns: 400px auto" element-loading-text="拼命加载中"> <div v-loading="loading" class="live-content" element-loading-text="拼命加载中">
<div style="background-color: #ffffff"> <div class="device-tree-container">
<DeviceTree :click-event="clickEvent" :context-menu-event="contextMenuEvent" /> <DeviceTree :click-event="clickEvent" :context-menu-event="contextMenuEvent" />
</div> </div>
<div style="display: grid; grid-template-rows: 5vh auto"> <div class="video-container">
<div style="font-size: 17px;line-height:5vh; display: grid; grid-template-columns: 1fr 1fr"> <div class="control-bar">
<div style="text-align: left"> <div class="split-controls">
分屏: 分屏:
<i class="iconfont icon-a-mti-1fenpingshi btn" :class="{active:spiltIndex === 0}" @click="spiltIndex=0" /> <i class="iconfont icon-a-mti-1fenpingshi btn" :class="{active:spiltIndex === 0}" @click="spiltIndex=0" />
<i class="iconfont icon-a-mti-4fenpingshi btn" :class="{active: spiltIndex === 1}" @click="spiltIndex=1" /> <i class="iconfont icon-a-mti-4fenpingshi btn" :class="{active: spiltIndex === 1}" @click="spiltIndex=1" />
<i class="iconfont icon-a-mti-6fenpingshi btn" :class="{active: spiltIndex === 2}" @click="spiltIndex=2" /> <i class="iconfont icon-a-mti-6fenpingshi btn" :class="{active: spiltIndex === 2}" @click="spiltIndex=2" />
<i class="iconfont icon-a-mti-9fenpingshi btn" :class="{active: spiltIndex === 3}" @click="spiltIndex=3" /> <i class="iconfont icon-a-mti-9fenpingshi btn" :class="{active: spiltIndex === 3}" @click="spiltIndex=3" />
</div> </div>
<div style="text-align: right; margin-right: 10px;"> <div class="fullscreen-control">
<i class="el-icon-full-screen btn" @click="fullScreen()" /> <i class="el-icon-full-screen btn" @click="fullScreen()" />
</div> </div>
</div> </div>
<div style="padding: 0; margin: 0 auto;"> <div class="player-container">
<div <div
ref="playBox" ref="playBox"
class="play-grid"
:style="liveStyle" :style="liveStyle"
> >
<div <div
@ -29,7 +30,7 @@
:class="getPlayerClass(spiltIndex, i)" :class="getPlayerClass(spiltIndex, i)"
@click="playerIdx = (i-1)" @click="playerIdx = (i-1)"
> >
<div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 15px;font-weight: bold;">{{ videoTip[i-1]?videoTip[i-1]:"无信号" }}</div> <div v-if="!videoUrl[i-1]" class="no-signal">{{ videoTip[i-1]?videoTip[i-1]:"无信号" }}</div>
<player <player
v-else v-else
:ref="'player'[i-1]" :ref="'player'[i-1]"
@ -111,21 +112,13 @@ export default {
computed: { computed: {
liveStyle() { liveStyle() {
if (!this.$store.getters.sidebar.opened) { return {
return { width: '151vh', height: '85vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns, display: 'grid',
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' } gridTemplateColumns: this.layout[this.spiltIndex].columns,
} else { gridTemplateRows: this.layout[this.spiltIndex].rows,
return { width: '140vh', height: '79vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns, gap: '4px',
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' } backgroundColor: '#a9a8a8'
} }
// this.$nextTick(() => {
// for (let i = 0; i < this.spilt; i++) {
// const player = this.$refs.player
// player && player[i] && player[i].updatePlayerDomSize()
// }
// })
// return style
} }
}, },
watch: { watch: {
@ -149,15 +142,37 @@ export default {
'$route.fullPath': 'checkPlayByParam' '$route.fullPath': 'checkPlayByParam'
}, },
mounted() { mounted() {
// Add window resize event listener to handle responsive behavior
window.addEventListener('resize', this.handleResize)
this.handleResize()
}, },
created() { created() {
this.checkPlayByParam() this.checkPlayByParam()
}, },
destroyed() { destroyed() {
clearTimeout(this.updateLooper) clearTimeout(this.updateLooper)
// Remove event listener when component is destroyed
window.removeEventListener('resize', this.handleResize)
}, },
methods: { methods: {
handleResize() {
// Force update to recalculate responsive layout
this.$forceUpdate()
// Resize any active players
this.$nextTick(() => {
for (let i = 0; i < this.layout[this.spiltIndex].spilt; i++) {
const playerRef = this.$refs[`player${i + 1}`]
if (playerRef) {
if (playerRef instanceof Array) {
playerRef[0].resize && playerRef[0].resize()
} else {
playerRef.resize && playerRef.resize()
}
}
}
})
},
destroy(idx) { destroy(idx) {
console.log(idx) console.log(idx)
this.clear(idx.substring(idx.length - 1)) this.clear(idx.substring(idx.length - 1))
@ -212,8 +227,6 @@ export default {
} }
}, },
shot(e) { shot(e) {
// console.log(e)
// send({code:'image',data:e})
var base64ToBlob = function(code) { var base64ToBlob = function(code) {
const parts = code.split(';base64,') const parts = code.split(';base64,')
const contentType = parts[0].split(':')[1] const contentType = parts[0].split(':')[1]
@ -257,9 +270,86 @@ export default {
} }
</script> </script>
<style> <style>
.live-container {
height: calc(100vh - 124px);
width: 100%;
}
.live-content {
height: 100%;
display: flex;
flex-direction: row;
}
.device-tree-container {
width: 300px;
min-width: 250px;
max-width: 400px;
background-color: #ffffff;
overflow: auto;
resize: horizontal;
}
@media (max-width: 768px) {
.live-content {
flex-direction: column;
}
.device-tree-container {
width: 100%;
max-width: 100%;
height: 200px;
min-height: 150px;
max-height: 300px;
resize: vertical;
}
}
.video-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.control-bar {
height: 5vh;
min-height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 17px;
}
.split-controls {
text-align: left;
padding-left: 10px;
}
.fullscreen-control {
text-align: right;
padding-right: 10px;
}
.player-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
overflow: hidden;
}
.play-grid {
width: 100%;
height: 100%;
max-height: calc(100vh - 180px);
aspect-ratio: 16/9;
}
.btn { .btn {
margin: 0 10px; margin: 0 10px;
cursor: pointer;
} }
.btn:hover { .btn:hover {
@ -268,7 +358,6 @@ export default {
.btn.active { .btn.active {
color: #409EFF; color: #409EFF;
} }
.redborder { .redborder {
@ -280,13 +369,39 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
} }
.no-signal {
color: #ffffff;
font-size: 15px;
font-weight: bold;
}
.play-box-2-1 { .play-box-2-1 {
grid-column: 1 / span 2; grid-column: 1 / span 2;
grid-row: 1 / span 2; grid-row: 1 / span 2;
} }
</style>
<style> /* Responsive adjustments for smaller screens */
@media (max-width: 576px) {
.control-bar {
flex-direction: column;
height: auto;
padding: 5px 0;
}
.split-controls, .fullscreen-control {
width: 100%;
text-align: center;
padding: 5px 0;
}
.btn {
margin: 0 5px;
}
}
.videoList { .videoList {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -1,12 +1,11 @@
'use strict' const path = require("path")
const path = require('path') const defaultSettings = require("./src/settings.js")
const defaultSettings = require('./src/settings.js')
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir) return path.join(__dirname, dir)
} }
const name = defaultSettings.title || 'WVP视频平台' // page title const name = defaultSettings.title || "WVP视频平台" // page title
// If your port is set to 80, // If your port is set to 80,
// use administrator privileges to execute the command line. // use administrator privileges to execute the command line.
@ -24,37 +23,37 @@ module.exports = {
* In most cases please use '/' !!! * In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath * Detail: https://cli.vuejs.org/config/#publicpath
*/ */
publicPath: '/', publicPath: "/",
outputDir: '../src/main/resources/static', outputDir: "../src/main/resources/static",
assetsDir: 'static', assetsDir: "static",
lintOnSave: process.env.NODE_ENV === 'development', lintOnSave: false, // Disable ESLint to avoid warnings
productionSourceMap: false, productionSourceMap: false,
devServer: { devServer: {
public: 'localhost:' + port, public: "localhost:" + port,
host: 'localhost', host: "localhost",
port: port, port: port,
open: true, open: true,
overlay: { overlay: {
warnings: false, warnings: false,
errors: true errors: false, // Changed to false to hide ESLint errors
}, },
// before: require('./mock/mock-server.js'), // before: require('./mock/mock-server.js'),
proxy: { proxy: {
'/dev-api': { "/dev-api": {
target: 'http://127.0.0.1:18080', target: "http://127.0.0.1:18080",
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/dev-api': '/' "^/dev-api": "/",
}
}, },
'/static/snap': { },
target: 'http://127.0.0.1:18080', "/static/snap": {
changeOrigin: true target: "http://127.0.0.1:18080",
changeOrigin: true,
// pathRewrite: { // pathRewrite: {
// '^/static/snap': '/static/snap' // '^/static/snap': '/static/snap'
// } // }
} },
} },
}, },
configureWebpack: { configureWebpack: {
// provide the app's title in webpack's name field, so that // provide the app's title in webpack's name field, so that
@ -62,80 +61,75 @@ module.exports = {
name: name, name: name,
resolve: { resolve: {
alias: { alias: {
'@': resolve('src') "@": resolve("src"),
} },
} },
}, },
chainWebpack(config) { chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload // it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').tap(() => [ config.plugin("preload").tap(() => [
{ {
rel: 'preload', rel: "preload",
// to ignore runtime.js // to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171 // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial' include: "initial",
} },
]) ])
// when there are many pages, it will cause too many meaningless requests // when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch') config.plugins.delete("prefetch")
// set svg-sprite-loader // set svg-sprite-loader
config.module.rule("svg").exclude.add(resolve("src/icons")).end()
config.module config.module
.rule('svg') .rule("icons")
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/) .test(/\.svg$/)
.include.add(resolve('src/icons')) .include.add(resolve("src/icons"))
.end() .end()
.use('svg-sprite-loader') .use("svg-sprite-loader")
.loader('svg-sprite-loader') .loader("svg-sprite-loader")
.options({ .options({
symbolId: 'icon-[name]' symbolId: "icon-[name]",
}) })
.end() .end()
config.when(process.env.NODE_ENV !== "development", (config) => {
config config
.when(process.env.NODE_ENV !== 'development', .plugin("ScriptExtHtmlWebpackPlugin")
config => { .after("html")
config .use("script-ext-html-webpack-plugin", [
.plugin('ScriptExtHtmlWebpackPlugin') {
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime` // `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/ inline: /runtime\..*\.js$/,
}]) },
])
.end() .end()
config config.optimization.splitChunks({
.optimization.splitChunks({ chunks: "all",
chunks: 'all',
cacheGroups: { cacheGroups: {
libs: { libs: {
name: 'chunk-libs', name: "chunk-libs",
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
priority: 10, priority: 10,
chunks: 'initial' // only package third parties that are initially dependent chunks: "initial", // only package third parties that are initially dependent
}, },
elementUI: { elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package name: "chunk-elementUI", // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
}, },
commons: { commons: {
name: 'chunk-commons', name: "chunk-commons",
test: resolve('src/components'), // can customize your rules test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number minChunks: 3, // minimum common number
priority: 5, priority: 5,
reuseExistingChunk: true reuseExistingChunk: true,
} },
} },
}) })
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single') config.optimization.runtimeChunk("single")
} })
) },
}
} }

View File

@ -89,15 +89,21 @@ Vue.prototype.$channelTypeList = {
new Vue({ new Vue({
beforeCreate: function () { beforeCreate: function () {
// 获取本平台的服务ID // 获取本平台的服务ID
console.log("获取本平台的服务ID")
if (!this.$myServerId) {
axios({ axios({
method: 'get', method: 'get',
url: `/api/server/system/configInfo`, url: `/api/server/system/configInfo`,
}).then( (res)=> { }).then( (res)=> {
if (res.data.code === 0) { if (res.data.code === 0) {
console.log(res.data)
console.log("当前服务ID " + res.data.data.addOn.serverId)
Vue.prototype.$myServerId = res.data.data.addOn.serverId; Vue.prototype.$myServerId = res.data.data.addOn.serverId;
} }
}).catch( (error)=> { }).catch( (error)=> {
}); });
}
}, },
router: router, router: router,
render: h => h(App), render: h => h(App),