package com.ard.zlm.service.impl; import com.alibaba.fastjson2.JSON; import com.ard.zlm.api.domain.StreamInfo; import com.ard.zlm.common.InviteSessionStatus; import com.ard.zlm.common.InviteSessionType; import com.ard.zlm.config.UserSetting; import com.ard.zlm.constants.VideoManagerConstants; import com.ard.zlm.domain.InviteInfo; import com.ard.zlm.service.ErrorCallback; import com.ard.zlm.service.IInviteStreamService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @Slf4j @Service public class InviteStreamServiceImpl implements IInviteStreamService { private final Map>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); @Autowired private RedisTemplate redisTemplate; @Autowired private UserSetting userSetting; @Override public void updateInviteInfo(InviteInfo inviteInfo) { if (InviteSessionStatus.ready == inviteInfo.getStatus()) { updateInviteInfo(inviteInfo, Long.valueOf(userSetting.getPlayTimeout()) * 2); } else { updateInviteInfo(inviteInfo, null); } } @Override public void updateInviteInfo(InviteInfo inviteInfo, Long time) { if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) { log.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo)); return; } InviteInfo inviteInfoForUpdate; if (InviteSessionStatus.ready == inviteInfo.getStatus()) { if (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null || inviteInfo.getType() == null || inviteInfo.getStream() == null ) { return; } inviteInfoForUpdate = inviteInfo; } else { InviteInfo inviteInfoInRedis = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInRedis == null) { log.warn("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}", inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); return; } if (inviteInfo.getStreamInfo() != null) { inviteInfoInRedis.setStreamInfo(inviteInfo.getStreamInfo()); } if (inviteInfo.getSsrcInfo() != null) { inviteInfoInRedis.setSsrcInfo(inviteInfo.getSsrcInfo()); } if (inviteInfo.getStreamMode() != null) { inviteInfoInRedis.setStreamMode(inviteInfo.getStreamMode()); } if (inviteInfo.getReceiveIp() != null) { inviteInfoInRedis.setReceiveIp(inviteInfo.getReceiveIp()); } if (inviteInfo.getReceivePort() != null) { inviteInfoInRedis.setReceivePort(inviteInfo.getReceivePort()); } if (inviteInfo.getStatus() != null) { inviteInfoInRedis.setStatus(inviteInfo.getStatus()); } inviteInfoForUpdate = inviteInfoInRedis; } if (inviteInfoForUpdate.getCreateTime() == null) { inviteInfoForUpdate.setCreateTime(System.currentTimeMillis()); } String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfoForUpdate.getType() + ":" + inviteInfoForUpdate.getChannelId() + ":" + inviteInfoForUpdate.getStream(); if (time != null && time > 0) { inviteInfoForUpdate.setExpirationTime(time); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoForUpdate); } @Override public InviteInfo updateInviteInfoForStream(InviteInfo inviteInfo, String stream) { InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInDb == null) { return null; } removeInviteInfo(inviteInfoInDb); String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + stream; inviteInfoInDb.setStream(stream); if (inviteInfoInDb.getSsrcInfo() != null) { inviteInfoInDb.getSsrcInfo().setStream(stream); } if (InviteSessionStatus.ready == inviteInfo.getStatus()) { inviteInfoInDb.setExpirationTime((long) (userSetting.getPlayTimeout() * 2)); } if (inviteInfoInDb.getCreateTime() == null) { inviteInfoInDb.setCreateTime(System.currentTimeMillis()); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoInDb); return inviteInfoInDb; } @Override public InviteInfo getInviteInfo(InviteSessionType type, Long channelId, String stream) { String key = VideoManagerConstants.INVITE_PREFIX; String keyPattern = (type != null ? type : "*") + ":" + (channelId != null ? channelId : "*") + ":" + (stream != null ? stream : "*"); ScanOptions options = ScanOptions.scanOptions().match(keyPattern).count(20).build(); try (Cursor> cursor = redisTemplate.opsForHash().scan(key, options)) { if (cursor.hasNext()) { return (InviteInfo) cursor.next().getValue(); } } catch (Exception e) { log.error("[Redis-InviteInfo] 查询异常: ", e); } return null; } @Override public List getAllInviteInfo() { List result = new ArrayList<>(); String key = VideoManagerConstants.INVITE_PREFIX; List values = redisTemplate.opsForHash().values(key); if (values.isEmpty()) { return result; } for (Object value : values) { result.add((InviteInfo) value); } return result; } @Override public InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, Long channelId) { return getInviteInfo(type, channelId, null); } @Override public InviteInfo getInviteInfoByStream(InviteSessionType type, String stream) { return getInviteInfo(type, null, stream); } @Override public void removeInviteInfo(InviteSessionType type, Long channelId, String stream) { String key = VideoManagerConstants.INVITE_PREFIX; if (type == null && channelId == null && stream == null) { redisTemplate.opsForHash().delete(key); return; } InviteInfo inviteInfo = getInviteInfo(type, channelId, stream); if (inviteInfo != null) { String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + inviteInfo.getStream(); redisTemplate.opsForHash().delete(key, objectKey); } } @Override public void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, Long channelId) { removeInviteInfo(inviteSessionType, channelId, null); } @Override public void removeInviteInfo(InviteInfo inviteInfo) { removeInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); } @Override public void once(InviteSessionType type, Long channelId, String stream, ErrorCallback callback) { String key = buildKey(type, channelId, stream); List> callbacks = inviteErrorCallbackMap.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()); callbacks.add(callback); } private String buildKey(InviteSessionType type, Long channelId, String stream) { String key = type + ":" + channelId; // 如果ssrc未null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite if (stream != null) { key += (":" + stream); } return key; } @Override public void clearInviteInfo(String deviceId) { List inviteInfoList = getAllInviteInfo(); for (InviteInfo inviteInfo : inviteInfoList) { if (inviteInfo.getDeviceId().equals(deviceId)) { removeInviteInfo(inviteInfo); } } } @Override public int getStreamInfoCount(String mediaServerId) { int count = 0; String key = VideoManagerConstants.INVITE_PREFIX; List values = redisTemplate.opsForHash().values(key); if (values.isEmpty()) { return count; } for (Object value : values) { InviteInfo inviteInfo = (InviteInfo) value; if (inviteInfo != null && inviteInfo.getStreamInfo() != null && inviteInfo.getStreamInfo().getMediaServer() != null && inviteInfo.getStreamInfo().getMediaServer().getId().equals(mediaServerId)) { if (inviteInfo.getType().equals(InviteSessionType.DOWNLOAD) && inviteInfo.getStreamInfo().getProgress() == 1) { continue; } count++; } } return count; } @Override public void call(InviteSessionType type, Long channelId, String stream, int code, String msg, StreamInfo data) { String key = buildSubStreamKey(type, channelId, stream); List> callbacks = inviteErrorCallbackMap.get(key); if (callbacks == null || callbacks.isEmpty()) { return; } for (ErrorCallback callback : callbacks) { if (callback != null) { callback.run(code, msg, data); } } inviteErrorCallbackMap.remove(key); } private String buildSubStreamKey(InviteSessionType type, Long channelId, String stream) { String key = type + ":" + channelId; if (stream != null) { key += (":" + stream); } return key; } @Override public InviteInfo getInviteInfoBySSRC(String ssrc) { List inviteInfoList = getAllInviteInfo(); if (inviteInfoList.isEmpty()) { return null; } for (InviteInfo inviteInfo : inviteInfoList) { if (inviteInfo.getSsrcInfo() != null && ssrc.equals(inviteInfo.getSsrcInfo().getSsrc())) { return inviteInfo; } } return null; } @Override public InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrc) { InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (inviteInfoInDb == null) { return null; } removeInviteInfo(inviteInfoInDb); String key = VideoManagerConstants.INVITE_PREFIX; String objectKey = inviteInfo.getType() + ":" + inviteInfo.getChannelId() + ":" + inviteInfo.getStream(); if (inviteInfoInDb.getSsrcInfo() != null) { inviteInfoInDb.getSsrcInfo().setSsrc(ssrc); } redisTemplate.opsForHash().put(key, objectKey, inviteInfoInDb); return inviteInfoInDb; } @Scheduled(fixedRate = 10000) //定时检测,清理错误的redis数据,防止因为错误数据导致的点播不可用 public void execute() { String key = VideoManagerConstants.INVITE_PREFIX; if (redisTemplate.opsForHash().size(key) == 0) { return; } List values = redisTemplate.opsForHash().values(key); for (Object value : values) { InviteInfo inviteInfo = (InviteInfo) value; if (inviteInfo.getStreamInfo() != null) { continue; } if (inviteInfo.getCreateTime() == null || inviteInfo.getExpirationTime() == 0) { removeInviteInfo(inviteInfo); } long time = inviteInfo.getCreateTime() + inviteInfo.getExpirationTime(); if (System.currentTimeMillis() > time) { removeInviteInfo(inviteInfo); } } } }