mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-22 21:37:48 +08:00
Compare commits
9 Commits
523e9fa2d8
...
8bdb311ee3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bdb311ee3 | ||
|
|
75b1e8deb2 | ||
|
|
0df557750f | ||
|
|
601d2b5d2b | ||
|
|
55f36f660b | ||
|
|
1a2de7a1c6 | ||
|
|
97c53f07c8 | ||
|
|
d825b986dd | ||
|
|
da98101aac |
@ -98,7 +98,7 @@ public class SubscribeHolder {
|
||||
for (Platform platform : platformList) {
|
||||
String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platform.getServerGBId());
|
||||
if (redisTemplate.hasKey(key)) {
|
||||
result.add(platform.getServerId());
|
||||
result.add(platform.getServerGBId());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -112,7 +112,7 @@ public class SubscribeHolder {
|
||||
for (Platform platform : platformList) {
|
||||
String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platform.getServerGBId());
|
||||
if (redisTemplate.hasKey(key)) {
|
||||
result.add(platform.getServerId());
|
||||
result.add(platform.getServerGBId());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -89,8 +89,8 @@ public interface PlatformMapper {
|
||||
@Select("SELECT * FROM wvp_platform WHERE id=#{id}")
|
||||
Platform query(int id);
|
||||
|
||||
@Update("UPDATE wvp_platform SET status=#{online} WHERE id=#{id}" )
|
||||
int updateStatus(@Param("id") int id, @Param("online") boolean online);
|
||||
@Update("UPDATE wvp_platform SET status=#{online}, server_id = #{serverId} WHERE id=#{id}" )
|
||||
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")
|
||||
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}")
|
||||
List<Platform> queryServerIdsWithEnableAndServer(@Param("serverId") String serverId);
|
||||
|
||||
@Update("UPDATE wvp_platform SET status=false" )
|
||||
void offlineAll();
|
||||
@Update("UPDATE wvp_platform SET status=false where server_id = #{serverId}" )
|
||||
void offlineAll(@Param("serverId") String serverId);
|
||||
|
||||
}
|
||||
|
||||
@ -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.MediaServerOnlineEvent;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -21,11 +22,12 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @description:Event事件通知推送器,支持推送在线事件、离线事件
|
||||
* @author: swwheihei
|
||||
* @date: 2020年5月6日 上午11:30:50
|
||||
* @date: 2020年5月6日 上午11:30:50
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class EventPublisher {
|
||||
|
||||
@ -72,12 +74,7 @@ public class EventPublisher {
|
||||
}
|
||||
public void catalogEventPublish(Platform platform, List<CommonGBChannel> deviceChannels, String type, boolean share) {
|
||||
if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) {
|
||||
// 指定了上级平台的推送,则发送到指定的设备,未指定的则全部发送, 接收后各自处理自己的
|
||||
CatalogEvent outEvent = new CatalogEvent(this);
|
||||
outEvent.setChannels(deviceChannels);
|
||||
outEvent.setType(type);
|
||||
outEvent.setPlatform(platform);
|
||||
redisRpcService.catalogEventPublish(platform.getServerId(), outEvent);
|
||||
log.info("[国标级联] 目录状态推送, 此上级平台由其他服务处理,消息已经忽略");
|
||||
return;
|
||||
}
|
||||
CatalogEvent outEvent = new CatalogEvent(this);
|
||||
@ -96,12 +93,11 @@ public class EventPublisher {
|
||||
}
|
||||
outEvent.setChannels(channels);
|
||||
outEvent.setType(type);
|
||||
outEvent.setPlatform(platform);
|
||||
applicationEventPublisher.publishEvent(outEvent);
|
||||
if (platform == null && share) {
|
||||
// 如果没指定上级平台,则推送消息到所有在线的wvp处理自己含有的平台的目录更新
|
||||
redisRpcService.catalogEventPublish(null, outEvent);
|
||||
if (platform != null) {
|
||||
outEvent.setPlatform(platform);
|
||||
}
|
||||
applicationEventPublisher.publishEvent(outEvent);
|
||||
|
||||
}
|
||||
|
||||
public void mobilePositionEventPublish(MobilePosition mobilePosition) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
||||
@ -39,22 +40,30 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
|
||||
@Autowired
|
||||
private SubscribeHolder subscribeHolder;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(CatalogEvent event) {
|
||||
SubscribeInfo subscribe = null;
|
||||
Platform parentPlatform = null;
|
||||
|
||||
Map<String, List<Platform>> parentPlatformMap = new HashMap<>();
|
||||
log.info("[Catalog事件: {}] 通道数量: {}", event.getType(), event.getChannels().size());
|
||||
Map<String, List<Platform>> platformMap = new HashMap<>();
|
||||
Map<String, CommonGBChannel> channelMap = new HashMap<>();
|
||||
if (event.getPlatform() != null) {
|
||||
parentPlatform = event.getPlatform();
|
||||
if (parentPlatform.getServerGBId() == null) {
|
||||
log.info("[Catalog事件: {}] 平台服务国标编码未找到", event.getType());
|
||||
return;
|
||||
}
|
||||
subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId());
|
||||
if (subscribe == null) {
|
||||
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
|
||||
return;
|
||||
}
|
||||
|
||||
}else {
|
||||
List<Platform> allPlatform = platformService.queryAll();
|
||||
List<Platform> allPlatform = platformService.queryAll(userSetting.getServerId());
|
||||
// 获取所用订阅
|
||||
List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform);
|
||||
if (event.getChannels() != null) {
|
||||
@ -62,10 +71,14 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
|
||||
for (CommonGBChannel deviceChannel : event.getChannels()) {
|
||||
List<Platform> parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId(
|
||||
deviceChannel.getGbId(), platforms);
|
||||
parentPlatformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB);
|
||||
platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB);
|
||||
channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel);
|
||||
}
|
||||
}else {
|
||||
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
|
||||
}
|
||||
}else {
|
||||
log.info("[Catalog事件: {}] 事件内通道数为0", event.getType());
|
||||
}
|
||||
}
|
||||
switch (event.getType()) {
|
||||
@ -74,32 +87,32 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
|
||||
case CatalogEvent.DEL:
|
||||
|
||||
if (parentPlatform != null) {
|
||||
List<CommonGBChannel> deviceChannelList = new ArrayList<>();
|
||||
List<CommonGBChannel> channels = new ArrayList<>();
|
||||
if (event.getChannels() != null) {
|
||||
deviceChannelList.addAll(event.getChannels());
|
||||
channels.addAll(event.getChannels());
|
||||
}
|
||||
if (!deviceChannelList.isEmpty()) {
|
||||
log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), deviceChannelList.size());
|
||||
if (!channels.isEmpty()) {
|
||||
log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), channels.size());
|
||||
try {
|
||||
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
|
||||
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, channels, subscribe, null);
|
||||
} catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
|
||||
IllegalAccessException e) {
|
||||
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}else if (!parentPlatformMap.keySet().isEmpty()) {
|
||||
for (String gbId : parentPlatformMap.keySet()) {
|
||||
List<Platform> parentPlatforms = parentPlatformMap.get(gbId);
|
||||
if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
|
||||
for (Platform platform : parentPlatforms) {
|
||||
}else if (!platformMap.keySet().isEmpty()) {
|
||||
for (String serverGbId : platformMap.keySet()) {
|
||||
List<Platform> platformList = platformMap.get(serverGbId);
|
||||
if (platformList != null && !platformList.isEmpty()) {
|
||||
for (Platform platform : platformList) {
|
||||
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
|
||||
if (subscribeInfo == null) {
|
||||
continue;
|
||||
}
|
||||
log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
|
||||
log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), serverGbId);
|
||||
List<CommonGBChannel> deviceChannelList = new ArrayList<>();
|
||||
CommonGBChannel deviceChannel = new CommonGBChannel();
|
||||
deviceChannel.setGbDeviceId(gbId);
|
||||
deviceChannel.setGbDeviceId(serverGbId);
|
||||
deviceChannelList.add(deviceChannel);
|
||||
try {
|
||||
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
|
||||
@ -108,6 +121,8 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}else if (!parentPlatformMap.keySet().isEmpty()) {
|
||||
for (String gbId : parentPlatformMap.keySet()) {
|
||||
List<Platform> parentPlatforms = parentPlatformMap.get(gbId);
|
||||
}else if (!platformMap.keySet().isEmpty()) {
|
||||
for (String gbId : platformMap.keySet()) {
|
||||
List<Platform> parentPlatforms = platformMap.get(gbId);
|
||||
if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
|
||||
for (Platform platform : parentPlatforms) {
|
||||
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
||||
@ -37,12 +38,15 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
|
||||
@Autowired
|
||||
private SubscribeHolder subscribeHolder;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MobilePositionEvent event) {
|
||||
if (event.getMobilePosition().getChannelId() == 0) {
|
||||
return;
|
||||
}
|
||||
List<Platform> allPlatforms = platformService.queryAll();
|
||||
List<Platform> allPlatforms = platformService.queryAll(userSetting.getServerId());
|
||||
// 获取所用订阅
|
||||
List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms);
|
||||
if (platforms.isEmpty()) {
|
||||
|
||||
@ -80,6 +80,6 @@ public interface IPlatformService {
|
||||
|
||||
void delete(Integer platformId, CommonCallback<Object> callback);
|
||||
|
||||
List<Platform> queryAll();
|
||||
List<Platform> queryAll(String serverId);
|
||||
|
||||
}
|
||||
|
||||
@ -192,6 +192,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
sync(device);
|
||||
}else {
|
||||
device.setServerId(userSetting.getServerId());
|
||||
if(!device.isOnLine()){
|
||||
device.setOnLine(true);
|
||||
device.setCreateTime(now);
|
||||
@ -307,15 +308,15 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
return;
|
||||
}
|
||||
for (Device device : deviceList) {
|
||||
if (device == null || !device.isOnLine()) {
|
||||
if (device == null || !device.isOnLine() || !device.getServerId().equals(userSetting.getServerId())) {
|
||||
continue;
|
||||
}
|
||||
if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
|
||||
log.info("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
log.debug("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
addCatalogSubscribe(device, null);
|
||||
}
|
||||
if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
|
||||
log.info("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
log.debug("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,30 +160,26 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
log.warn("[多个通道离线] 通道数量为0,更新失败");
|
||||
return 0;
|
||||
}
|
||||
List<CommonGBChannel> onlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "ON");
|
||||
if (onlineChannelList.isEmpty()) {
|
||||
log.info("[多个通道离线] 更新失败, 参数内通道已经离线, 无需更新");
|
||||
return 0;
|
||||
}
|
||||
log.info("[通道离线] 共 {} 个", commonGBChannelList.size());
|
||||
int limitCount = 1000;
|
||||
int result = 0;
|
||||
if (onlineChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < onlineChannelList.size(); i += limitCount) {
|
||||
if (commonGBChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > onlineChannelList.size()) {
|
||||
toIndex = onlineChannelList.size();
|
||||
if (i + limitCount > commonGBChannelList.size()) {
|
||||
toIndex = commonGBChannelList.size();
|
||||
}
|
||||
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList.subList(i, toIndex), "OFF");
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF");
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList, "OFF");
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF");
|
||||
}
|
||||
if (result > 0) {
|
||||
try {
|
||||
// 发送catalog
|
||||
eventPublisher.catalogEventPublish(null, onlineChannelList, CatalogEvent.OFF);
|
||||
eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.OFF);
|
||||
} catch (Exception e) {
|
||||
log.warn("[多个通道离线] 发送失败,数量:{}", onlineChannelList.size(), e);
|
||||
log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -214,32 +210,25 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
log.warn("[多个通道上线] 通道数量为0,更新失败");
|
||||
return 0;
|
||||
}
|
||||
List<CommonGBChannel> offlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "OFF");
|
||||
if (offlineChannelList.isEmpty()) {
|
||||
log.warn("[多个通道上线] 更新失败, 参数内通道已经上线");
|
||||
return 0;
|
||||
}
|
||||
// 批量更新
|
||||
int limitCount = 1000;
|
||||
int result = 0;
|
||||
if (offlineChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < offlineChannelList.size(); i += limitCount) {
|
||||
if (commonGBChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > offlineChannelList.size()) {
|
||||
toIndex = offlineChannelList.size();
|
||||
if (i + limitCount > commonGBChannelList.size()) {
|
||||
toIndex = commonGBChannelList.size();
|
||||
}
|
||||
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList.subList(i, toIndex), "ON");
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON");
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList, "ON");
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON");
|
||||
}
|
||||
if (result > 0) {
|
||||
try {
|
||||
// 发送catalog
|
||||
eventPublisher.catalogEventPublish(null, offlineChannelList, CatalogEvent.ON);
|
||||
} catch (Exception e) {
|
||||
log.warn("[多个通道上线] 发送失败,数量:{}", offlineChannelList.size(), e);
|
||||
}
|
||||
try {
|
||||
// 发送catalog
|
||||
eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.ON);
|
||||
} catch (Exception e) {
|
||||
log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@ -63,11 +63,6 @@ import java.util.concurrent.TimeUnit;
|
||||
@Order(value=15)
|
||||
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
|
||||
private PlatformMapper platformMapper;
|
||||
|
||||
@ -133,7 +128,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
sendUnRegister(platform, taskInfo.getSipTransactionInfo());
|
||||
}
|
||||
// 启动时所有平台默认离线
|
||||
platformMapper.offlineAll();
|
||||
platformMapper.offlineAll(userSetting.getServerId());
|
||||
}
|
||||
@Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次
|
||||
public void statusLostCheck(){
|
||||
@ -199,6 +194,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
return;
|
||||
}
|
||||
log.info("[集群] 检测到 {} 已离线", serverId);
|
||||
redisCatchStorage.removeOfflineWVPInfo(serverId);
|
||||
String chooseServerId = redisCatchStorage.chooseOneServer(serverId);
|
||||
if (!userSetting.getServerId().equals(chooseServerId)){
|
||||
return;
|
||||
@ -390,8 +386,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
|
||||
this::keepaliveExpire);
|
||||
statusTaskRunner.addKeepAliveTask(keepaliveTask);
|
||||
|
||||
platformMapper.updateStatus(platform.getId(), true);
|
||||
platformMapper.updateStatus(platform.getId(), true, userSetting.getServerId());
|
||||
|
||||
if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) {
|
||||
if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) {
|
||||
@ -481,7 +476,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
|
||||
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
|
||||
|
||||
platformMapper.updateStatus(platform.getId(), false);
|
||||
platformMapper.updateStatus(platform.getId(), false, userSetting.getServerId());
|
||||
|
||||
// 停止所有推流
|
||||
log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId());
|
||||
@ -521,7 +516,6 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
gpsMsgInfo = null;
|
||||
}
|
||||
|
||||
|
||||
if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){
|
||||
gpsMsgInfo = new GPSMsgInfo();
|
||||
gpsMsgInfo.setId(channel.getGbDeviceId());
|
||||
@ -870,7 +864,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Platform> queryAll() {
|
||||
return platformMapper.queryAll();
|
||||
public List<Platform> queryAll(String serverId) {
|
||||
return platformMapper.queryByServerId(serverId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId());
|
||||
if (inviteInfoInCatch != null ) {
|
||||
if (inviteInfoInCatch.getStreamInfo() == null) {
|
||||
// 释放生成的ssrc,使用上一次申请的322
|
||||
// 释放生成的ssrc,使用上一次申请的
|
||||
|
||||
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
|
||||
// 点播发起了但是尚未成功, 仅注册回调等待结果即可
|
||||
|
||||
@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.session;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -13,6 +14,7 @@ import java.util.Set;
|
||||
/**
|
||||
* ssrc使用
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SSRCFactory {
|
||||
|
||||
@ -93,6 +95,7 @@ public class SSRCFactory {
|
||||
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
|
||||
Long size = redisTemplate.opsForSet().size(redisKey);
|
||||
if (size == null || size == 0) {
|
||||
log.info("[获取 SSRC 失败] redisKey: {}", redisKey);
|
||||
throw new RuntimeException("ssrc已经用完");
|
||||
} else {
|
||||
// 在集合中移除并返回一个随机成员。
|
||||
|
||||
@ -143,39 +143,44 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc());
|
||||
}
|
||||
}
|
||||
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
if (mediaServer != null) {
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId());
|
||||
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
|
||||
// 来自上级平台的停止对讲
|
||||
log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
|
||||
audioBroadcastManager.del(sendRtpItem.getChannelId());
|
||||
}
|
||||
if (sendRtpItem.getServerId().equals(userSetting.getServerId())) {
|
||||
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
if (mediaServer != null) {
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId());
|
||||
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
|
||||
// 来自上级平台的停止对讲
|
||||
log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
|
||||
audioBroadcastManager.del(sendRtpItem.getChannelId());
|
||||
}
|
||||
|
||||
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId);
|
||||
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId);
|
||||
|
||||
if (mediaInfo.getReaderCount() <= 0) {
|
||||
log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
|
||||
Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId());
|
||||
if (device == null) {
|
||||
log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
|
||||
return;
|
||||
}
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId());
|
||||
if (deviceChannel == null) {
|
||||
log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
|
||||
cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
|
||||
if (mediaInfo != null && mediaInfo.getReaderCount() <= 0) {
|
||||
log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
|
||||
Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId());
|
||||
if (device == null) {
|
||||
log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
|
||||
return;
|
||||
}
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId());
|
||||
if (deviceChannel == null) {
|
||||
log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
|
||||
cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO 流再其他wvp上时应该通知这个wvp停止推流和发送BYE
|
||||
|
||||
}
|
||||
}
|
||||
// 可能是设备发送的停止
|
||||
|
||||
@ -172,10 +172,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
// 点播成功, TODO 可以在此处检测cancel命令是否存在,存在则不发送
|
||||
if (userSetting.getUseCustomSsrcForParentInvite()) {
|
||||
// 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
|
||||
String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName())
|
||||
MediaServer mediaServer = mediaServerService.getOne(streamInfo.getMediaServer().getId());
|
||||
if (mediaServer != null) {
|
||||
String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName())
|
||||
? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId())
|
||||
: ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId());
|
||||
inviteInfo.setSsrc(ssrc);
|
||||
: ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId());
|
||||
inviteInfo.setSsrc(ssrc);
|
||||
}
|
||||
}
|
||||
// 构建sendRTP内容
|
||||
SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(),
|
||||
|
||||
@ -26,7 +26,7 @@ public interface IRedisRpcPlayService {
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -93,11 +93,11 @@ public class RedisAlarmMsgListener implements MessageListener {
|
||||
log.warn("[REDIS的ALARM通知]消息解析失败");
|
||||
continue;
|
||||
}
|
||||
String gbId = alarmChannelMessage.getGbId();
|
||||
String chanelId = alarmChannelMessage.getGbId();
|
||||
|
||||
DeviceAlarm deviceAlarm = new DeviceAlarm();
|
||||
deviceAlarm.setCreateTime(DateUtil.getNow());
|
||||
deviceAlarm.setChannelId(gbId);
|
||||
deviceAlarm.setChannelId(chanelId);
|
||||
deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
|
||||
deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
|
||||
deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
|
||||
@ -106,7 +106,7 @@ public class RedisAlarmMsgListener implements MessageListener {
|
||||
deviceAlarm.setLongitude(0);
|
||||
deviceAlarm.setLatitude(0);
|
||||
|
||||
if (ObjectUtils.isEmpty(gbId)) {
|
||||
if (ObjectUtils.isEmpty(chanelId)) {
|
||||
if (userSetting.getSendToPlatformsWhenIdLost()) {
|
||||
// 发送给所有的上级
|
||||
List<Platform> parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId());
|
||||
@ -148,24 +148,26 @@ public class RedisAlarmMsgListener implements MessageListener {
|
||||
}
|
||||
} else {
|
||||
// 获取该通道ID是属于设备还是对应的上级平台
|
||||
Device device = deviceService.getDeviceBySourceChannelDeviceId(gbId);
|
||||
List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(gbId);
|
||||
if (device != null && (platforms == null || platforms.isEmpty())) {
|
||||
Device device = deviceService.getDeviceBySourceChannelDeviceId(chanelId);
|
||||
List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(chanelId);
|
||||
if (device != null && device.getServerId().equals(userSetting.getServerId()) && (platforms == null || platforms.isEmpty())) {
|
||||
try {
|
||||
commander.sendAlarmMessage(device, deviceAlarm);
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
|
||||
}
|
||||
} else if (device == null && (platforms != null && !platforms.isEmpty())) {
|
||||
} else if (device == null && (platforms != null && !platforms.isEmpty() )) {
|
||||
for (Platform platform : platforms) {
|
||||
try {
|
||||
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
|
||||
if (platform.getServerId().equals(userSetting.getServerId())) {
|
||||
try {
|
||||
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("[REDIS的ALARM通知] 未查询到" + gbId + "所属的平台或设备");
|
||||
log.warn("[REDIS的ALARM通知] 未查询到" + chanelId + "所属的平台或设备");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -48,7 +48,7 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
|
||||
|
||||
@Override
|
||||
public void onMessage(Message message, byte[] bytes) {
|
||||
log.info("[REDIS: 流设备状态变化]: {}", new String(message.getBody()));
|
||||
log.info("[REDIS: 推流设备状态变化]: {}", new String(message.getBody()));
|
||||
taskQueue.offer(message);
|
||||
}
|
||||
|
||||
@ -84,11 +84,13 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
|
||||
if (streamStatusMessage.getOfflineStreams() != null
|
||||
&& !streamStatusMessage.getOfflineStreams().isEmpty()) {
|
||||
// 更新部分设备离线
|
||||
log.info("[REDIS: 推流设备状态变化] 更新部分设备离线: {}个", streamStatusMessage.getOfflineStreams().size());
|
||||
streamPushService.offline(streamStatusMessage.getOfflineStreams());
|
||||
}
|
||||
if (streamStatusMessage.getOnlineStreams() != null &&
|
||||
!streamStatusMessage.getOnlineStreams().isEmpty()) {
|
||||
// 更新部分设备上线
|
||||
log.info("[REDIS: 推流设备状态变化] 更新部分设备上线: {}个", streamStatusMessage.getOnlineStreams().size());
|
||||
streamPushService.online(streamStatusMessage.getOnlineStreams());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -181,7 +181,8 @@ public class RedisRpcStreamPushController extends RpcController {
|
||||
*/
|
||||
@RedisRpcMapping("play")
|
||||
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();
|
||||
if (id <= 0) {
|
||||
response.setStatusCode(ErrorCode.ERROR400.getCode());
|
||||
|
||||
@ -193,8 +193,11 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playPush(Integer id, ErrorCallback<StreamInfo> callback) {
|
||||
RedisRpcRequest request = buildRequest("streamPush/play", id);
|
||||
public void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback) {
|
||||
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);
|
||||
if (response == null) {
|
||||
callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null);
|
||||
|
||||
@ -27,6 +27,8 @@ public interface IRedisCatchStorage {
|
||||
*/
|
||||
void updateWVPInfo(ServerInfo serverInfo, int time);
|
||||
|
||||
void removeOfflineWVPInfo(String serverId);
|
||||
|
||||
/**
|
||||
* 发送推流生成与推流消失消息
|
||||
* @param jsonObject 消息内容
|
||||
|
||||
@ -84,6 +84,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
||||
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
|
||||
public void sendStreamChangeMsg(String type, JSONObject jsonObject) {
|
||||
String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type;
|
||||
|
||||
@ -23,6 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ -190,12 +193,23 @@ public class StreamProxyController {
|
||||
@ResponseBody
|
||||
@Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "id", description = "代理Id", required = true)
|
||||
public StreamContent start(int id){
|
||||
public StreamContent start(HttpServletRequest request, int id){
|
||||
log.info("播放代理: {}", id);
|
||||
StreamInfo streamInfo = streamProxyPlayService.start(id, null, null);
|
||||
if (streamInfo == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
|
||||
}else {
|
||||
if (userSetting.getUseSourceIpAsStreamIp()) {
|
||||
streamInfo=streamInfo.clone();//深拷贝
|
||||
String host;
|
||||
try {
|
||||
URL url=new URL(request.getRequestURL().toString());
|
||||
host=url.getHost();
|
||||
} catch (MalformedURLException e) {
|
||||
host=request.getLocalAddr();
|
||||
}
|
||||
streamInfo.changeStreamIp(host);
|
||||
}
|
||||
return new StreamContent(streamInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,8 +35,11 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -245,7 +248,7 @@ public class StreamPushController {
|
||||
@GetMapping(value = "/start")
|
||||
@ResponseBody
|
||||
@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");
|
||||
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
|
||||
result.onTimeout(()->{
|
||||
@ -254,6 +257,17 @@ public class StreamPushController {
|
||||
});
|
||||
streamPushPlayService.start(id, (code, msg, streamInfo) -> {
|
||||
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));
|
||||
result.setResult(success);
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ public interface StreamPushMapper {
|
||||
"(#{item.app}, #{item.stream}) " +
|
||||
"</foreach>" +
|
||||
")</script>")
|
||||
List<StreamPush> getListFromRedis(List<StreamPushItemFromRedis> offlineStreams);
|
||||
List<StreamPush> getListInList(List<StreamPushItemFromRedis> offlineStreams);
|
||||
|
||||
|
||||
@Select("SELECT CONCAT(app,stream) from wvp_stream_push")
|
||||
|
||||
@ -58,7 +58,7 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService {
|
||||
Assert.notNull(streamPush, "推流信息未找到");
|
||||
|
||||
if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) {
|
||||
redisRpcPlayService.playPush(id, callback);
|
||||
redisRpcPlayService.playPush(streamPush.getServerId(), id, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -458,16 +458,27 @@ public class StreamPushServiceImpl implements IStreamPushService {
|
||||
@Override
|
||||
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);
|
||||
gbChannelService.offline(commonGBChannelList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void online(List<StreamPushItemFromRedis> onlineStreams) {
|
||||
if (onlineStreams.isEmpty()) {
|
||||
log.info("[设备上线] 推流设备列表为空");
|
||||
return;
|
||||
}
|
||||
// 更新部分设备上线streamPushService
|
||||
List<StreamPush> streamPushList = streamPushMapper.getListFromRedis(onlineStreams);
|
||||
List<StreamPush> streamPushList = streamPushMapper.getListInList(onlineStreams);
|
||||
if (streamPushList.isEmpty()) {
|
||||
for (StreamPushItemFromRedis onlineStream : onlineStreams) {
|
||||
log.info("[设备上线] 未查询到这些通道: {}/{}", onlineStream.getApp(), onlineStream.getStream());
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList);
|
||||
|
||||
@ -861,7 +861,7 @@
|
||||
320623,如东县,3206
|
||||
320681,启东市,3206
|
||||
320682,如皋市,3206
|
||||
320684,海门市,3206
|
||||
320684,海门区,3206
|
||||
320685,海安市,3206
|
||||
3207,连云港市,32
|
||||
320703,连云区,3207
|
||||
@ -918,8 +918,6 @@
|
||||
33,浙江省,
|
||||
3301,杭州市,33
|
||||
330102,上城区,3301
|
||||
330103,下城区,3301
|
||||
330104,江干区,3301
|
||||
330105,拱墅区,3301
|
||||
330106,西湖区,3301
|
||||
330108,滨江区,3301
|
||||
@ -927,6 +925,8 @@
|
||||
330110,余杭区,3301
|
||||
330111,富阳区,3301
|
||||
330112,临安区,3301
|
||||
330113,临平区,3301
|
||||
330114,钱塘区,3301
|
||||
330122,桐庐县,3301
|
||||
330127,淳安县,3301
|
||||
330182,建德市,3301
|
||||
|
||||
|
232
web/.eslintrc.js
232
web/.eslintrc.js
@ -1,198 +1,48 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: 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: {
|
||||
"vue/max-attributes-per-line": [2, {
|
||||
"singleline": 10,
|
||||
"multiline": {
|
||||
"max": 1,
|
||||
"allowFirstLine": false
|
||||
}
|
||||
}],
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/multiline-html-element-content-newline":"off",
|
||||
"vue/name-property-casing": ["error", "PascalCase"],
|
||||
"vue/no-v-html": "off",
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'block-spacing': [2, 'always'],
|
||||
'brace-style': [2, '1tbs', {
|
||||
'allowSingleLine': true
|
||||
}],
|
||||
'camelcase': [0, {
|
||||
'properties': 'always'
|
||||
}],
|
||||
'comma-dangle': [2, 'never'],
|
||||
'comma-spacing': [2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'comma-style': [2, 'last'],
|
||||
'constructor-super': 2,
|
||||
'curly': [2, 'multi-line'],
|
||||
'dot-location': [2, 'property'],
|
||||
'eol-last': 2,
|
||||
'eqeqeq': ["error", "always", {"null": "ignore"}],
|
||||
'generator-star-spacing': [2, {
|
||||
'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']
|
||||
}
|
||||
// Disable or downgrade problematic rules
|
||||
"vue/require-prop-types": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/no-unused-vars": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"no-undef": "warn",
|
||||
eqeqeq: "warn",
|
||||
"no-return-assign": "warn",
|
||||
"new-cap": "warn",
|
||||
"vue/html-self-closing": "off",
|
||||
"vue/html-closing-bracket-spacing": "off",
|
||||
"vue/this-in-template": "off",
|
||||
"vue/require-v-for-key": "warn",
|
||||
"vue/valid-v-model": "warn",
|
||||
"vue/attributes-order": "off",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
|
||||
// Style rules - make them warnings instead of errors
|
||||
quotes: ["warn", "single"],
|
||||
"comma-dangle": ["warn", "never"],
|
||||
"space-in-parens": "warn",
|
||||
"comma-spacing": "warn",
|
||||
"object-curly-spacing": "warn",
|
||||
"arrow-spacing": "warn",
|
||||
semi: ["warn", "never"],
|
||||
"no-multi-spaces": "warn",
|
||||
|
||||
// Turn off console warnings for development
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
},
|
||||
globals: {
|
||||
// Define global variables to prevent 'undefined' errors
|
||||
ZLMRTCClient: "readonly",
|
||||
jessibuca: "readonly",
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div id="DeviceTree" style="width: 100%;height: 100%; background-color: #FFFFFF; overflow: auto; padding: 30px">
|
||||
<div style="height: 30px; display: grid; grid-template-columns: auto auto">
|
||||
<div>通道列表</div>
|
||||
<div>
|
||||
<div id="DeviceTree" class="device-tree-container">
|
||||
<div class="device-tree-header">
|
||||
<div class="header-title">通道列表</div>
|
||||
<div class="header-switch">
|
||||
<el-switch
|
||||
v-model="showRegion"
|
||||
active-color="#13ce66"
|
||||
@ -12,9 +12,27 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<RegionTree v-if="showRegion" ref="regionTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
|
||||
<GroupTree v-if="!showRegion" ref="groupTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
|
||||
<div class="tree-content">
|
||||
<div class="tree-wrapper">
|
||||
<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>
|
||||
</template>
|
||||
@ -26,7 +44,24 @@ import GroupTree from './GroupTree.vue'
|
||||
export default {
|
||||
name: 'DeviceTree',
|
||||
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() {
|
||||
return {
|
||||
showRegion: true,
|
||||
@ -37,15 +72,67 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
// if (this.jessibuca) {
|
||||
// this.jessibuca.destroy();
|
||||
// }
|
||||
// this.playing = false;
|
||||
// this.loaded = false;
|
||||
// this.performance = "";
|
||||
mounted() {
|
||||
// Apply fix for Element UI tree scrollbars after component is mounted
|
||||
this.$nextTick(() => {
|
||||
this.fixTreeScrollbars()
|
||||
this.adjustTreeHeight()
|
||||
|
||||
// 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: {
|
||||
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) {
|
||||
},
|
||||
treeNodeClickEvent: function(data) {
|
||||
@ -62,13 +149,130 @@ export default {
|
||||
</script>
|
||||
|
||||
<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;
|
||||
}
|
||||
.device-online{
|
||||
|
||||
.device-online {
|
||||
color: #252525;
|
||||
}
|
||||
.device-offline{
|
||||
|
||||
.device-offline {
|
||||
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>
|
||||
|
||||
@ -1,25 +1,26 @@
|
||||
<template>
|
||||
<div id="live" style="height: calc(100vh - 124px)">
|
||||
<div v-loading="loading" style="height: 100%; display: grid; grid-template-columns: 400px auto" element-loading-text="拼命加载中">
|
||||
<div style="background-color: #ffffff">
|
||||
<div id="live" class="live-container">
|
||||
<div v-loading="loading" class="live-content" element-loading-text="拼命加载中">
|
||||
<div class="device-tree-container">
|
||||
<DeviceTree :click-event="clickEvent" :context-menu-event="contextMenuEvent" />
|
||||
</div>
|
||||
<div style="display: grid; grid-template-rows: 5vh auto">
|
||||
<div style="font-size: 17px;line-height:5vh; display: grid; grid-template-columns: 1fr 1fr">
|
||||
<div style="text-align: left">
|
||||
<div class="video-container">
|
||||
<div class="control-bar">
|
||||
<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-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-9fenpingshi btn" :class="{active: spiltIndex === 3}" @click="spiltIndex=3" />
|
||||
</div>
|
||||
<div style="text-align: right; margin-right: 10px;">
|
||||
<div class="fullscreen-control">
|
||||
<i class="el-icon-full-screen btn" @click="fullScreen()" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 0; margin: 0 auto;">
|
||||
<div class="player-container">
|
||||
<div
|
||||
ref="playBox"
|
||||
class="play-grid"
|
||||
:style="liveStyle"
|
||||
>
|
||||
<div
|
||||
@ -29,7 +30,7 @@
|
||||
:class="getPlayerClass(spiltIndex, i)"
|
||||
@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
|
||||
v-else
|
||||
:ref="'player'[i-1]"
|
||||
@ -111,21 +112,13 @@ export default {
|
||||
|
||||
computed: {
|
||||
liveStyle() {
|
||||
if (!this.$store.getters.sidebar.opened) {
|
||||
return { width: '151vh', height: '85vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns,
|
||||
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' }
|
||||
} else {
|
||||
return { width: '140vh', height: '79vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns,
|
||||
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' }
|
||||
return {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: this.layout[this.spiltIndex].columns,
|
||||
gridTemplateRows: this.layout[this.spiltIndex].rows,
|
||||
gap: '4px',
|
||||
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: {
|
||||
@ -149,15 +142,37 @@ export default {
|
||||
'$route.fullPath': 'checkPlayByParam'
|
||||
},
|
||||
mounted() {
|
||||
|
||||
// Add window resize event listener to handle responsive behavior
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.handleResize()
|
||||
},
|
||||
created() {
|
||||
this.checkPlayByParam()
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.updateLooper)
|
||||
// Remove event listener when component is destroyed
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
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) {
|
||||
console.log(idx)
|
||||
this.clear(idx.substring(idx.length - 1))
|
||||
@ -212,8 +227,6 @@ export default {
|
||||
}
|
||||
},
|
||||
shot(e) {
|
||||
// console.log(e)
|
||||
// send({code:'image',data:e})
|
||||
var base64ToBlob = function(code) {
|
||||
const parts = code.split(';base64,')
|
||||
const contentType = parts[0].split(':')[1]
|
||||
@ -257,9 +270,86 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<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 {
|
||||
margin: 0 10px;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
@ -268,7 +358,6 @@ export default {
|
||||
|
||||
.btn.active {
|
||||
color: #409EFF;
|
||||
|
||||
}
|
||||
|
||||
.redborder {
|
||||
@ -280,13 +369,39 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.no-signal {
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.play-box-2-1 {
|
||||
grid-column: 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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const defaultSettings = require('./src/settings.js')
|
||||
const path = require("path")
|
||||
const defaultSettings = require("./src/settings.js")
|
||||
|
||||
function resolve(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,
|
||||
// use administrator privileges to execute the command line.
|
||||
@ -24,37 +23,37 @@ module.exports = {
|
||||
* In most cases please use '/' !!!
|
||||
* Detail: https://cli.vuejs.org/config/#publicpath
|
||||
*/
|
||||
publicPath: '/',
|
||||
outputDir: '../src/main/resources/static',
|
||||
assetsDir: 'static',
|
||||
lintOnSave: process.env.NODE_ENV === 'development',
|
||||
publicPath: "/",
|
||||
outputDir: "../src/main/resources/static",
|
||||
assetsDir: "static",
|
||||
lintOnSave: false, // Disable ESLint to avoid warnings
|
||||
productionSourceMap: false,
|
||||
devServer: {
|
||||
public: 'localhost:' + port,
|
||||
host: 'localhost',
|
||||
public: "localhost:" + port,
|
||||
host: "localhost",
|
||||
port: port,
|
||||
open: true,
|
||||
overlay: {
|
||||
warnings: false,
|
||||
errors: true
|
||||
errors: false, // Changed to false to hide ESLint errors
|
||||
},
|
||||
// before: require('./mock/mock-server.js'),
|
||||
proxy: {
|
||||
'/dev-api': {
|
||||
target: 'http://127.0.0.1:18080',
|
||||
"/dev-api": {
|
||||
target: "http://127.0.0.1:18080",
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/dev-api': '/'
|
||||
}
|
||||
"^/dev-api": "/",
|
||||
},
|
||||
},
|
||||
'/static/snap': {
|
||||
target: 'http://127.0.0.1:18080',
|
||||
changeOrigin: true
|
||||
"/static/snap": {
|
||||
target: "http://127.0.0.1:18080",
|
||||
changeOrigin: true,
|
||||
// pathRewrite: {
|
||||
// '^/static/snap': '/static/snap'
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
// provide the app's title in webpack's name field, so that
|
||||
@ -62,80 +61,75 @@ module.exports = {
|
||||
name: name,
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src')
|
||||
}
|
||||
}
|
||||
"@": resolve("src"),
|
||||
},
|
||||
},
|
||||
},
|
||||
chainWebpack(config) {
|
||||
// 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
|
||||
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
|
||||
include: 'initial'
|
||||
}
|
||||
include: "initial",
|
||||
},
|
||||
])
|
||||
|
||||
// when there are many pages, it will cause too many meaningless requests
|
||||
config.plugins.delete('prefetch')
|
||||
config.plugins.delete("prefetch")
|
||||
|
||||
// set svg-sprite-loader
|
||||
config.module.rule("svg").exclude.add(resolve("src/icons")).end()
|
||||
config.module
|
||||
.rule('svg')
|
||||
.exclude.add(resolve('src/icons'))
|
||||
.end()
|
||||
config.module
|
||||
.rule('icons')
|
||||
.rule("icons")
|
||||
.test(/\.svg$/)
|
||||
.include.add(resolve('src/icons'))
|
||||
.include.add(resolve("src/icons"))
|
||||
.end()
|
||||
.use('svg-sprite-loader')
|
||||
.loader('svg-sprite-loader')
|
||||
.use("svg-sprite-loader")
|
||||
.loader("svg-sprite-loader")
|
||||
.options({
|
||||
symbolId: 'icon-[name]'
|
||||
symbolId: "icon-[name]",
|
||||
})
|
||||
.end()
|
||||
|
||||
config
|
||||
.when(process.env.NODE_ENV !== 'development',
|
||||
config => {
|
||||
config
|
||||
.plugin('ScriptExtHtmlWebpackPlugin')
|
||||
.after('html')
|
||||
.use('script-ext-html-webpack-plugin', [{
|
||||
config.when(process.env.NODE_ENV !== "development", (config) => {
|
||||
config
|
||||
.plugin("ScriptExtHtmlWebpackPlugin")
|
||||
.after("html")
|
||||
.use("script-ext-html-webpack-plugin", [
|
||||
{
|
||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
||||
inline: /runtime\..*\.js$/
|
||||
}])
|
||||
.end()
|
||||
config
|
||||
.optimization.splitChunks({
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
libs: {
|
||||
name: 'chunk-libs',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: 10,
|
||||
chunks: 'initial' // only package third parties that are initially dependent
|
||||
},
|
||||
elementUI: {
|
||||
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
|
||||
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
|
||||
},
|
||||
commons: {
|
||||
name: 'chunk-commons',
|
||||
test: resolve('src/components'), // can customize your rules
|
||||
minChunks: 3, // minimum common number
|
||||
priority: 5,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
})
|
||||
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
|
||||
config.optimization.runtimeChunk('single')
|
||||
}
|
||||
)
|
||||
}
|
||||
inline: /runtime\..*\.js$/,
|
||||
},
|
||||
])
|
||||
.end()
|
||||
config.optimization.splitChunks({
|
||||
chunks: "all",
|
||||
cacheGroups: {
|
||||
libs: {
|
||||
name: "chunk-libs",
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: 10,
|
||||
chunks: "initial", // only package third parties that are initially dependent
|
||||
},
|
||||
elementUI: {
|
||||
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
|
||||
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
|
||||
},
|
||||
commons: {
|
||||
name: "chunk-commons",
|
||||
test: resolve("src/components"), // can customize your rules
|
||||
minChunks: 3, // minimum common number
|
||||
priority: 5,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
|
||||
config.optimization.runtimeChunk("single")
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@ -89,15 +89,21 @@ Vue.prototype.$channelTypeList = {
|
||||
new Vue({
|
||||
beforeCreate: function () {
|
||||
// 获取本平台的服务ID
|
||||
axios({
|
||||
method: 'get',
|
||||
url: `/api/server/system/configInfo`,
|
||||
}).then( (res)=> {
|
||||
if (res.data.code === 0) {
|
||||
Vue.prototype.$myServerId = res.data.data.addOn.serverId;
|
||||
}
|
||||
}).catch( (error)=> {
|
||||
});
|
||||
console.log("获取本平台的服务ID")
|
||||
if (!this.$myServerId) {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: `/api/server/system/configInfo`,
|
||||
}).then( (res)=> {
|
||||
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;
|
||||
}
|
||||
}).catch( (error)=> {
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
router: router,
|
||||
render: h => h(App),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user