Merge branch 'develop-add-api-key' of https://github.com/ancienter/wvp-GB28181-pro into develop-add-api-key

pull/1389/head
leesam 2024-04-09 09:59:06 +08:00
commit b6d70c6807
23 changed files with 227 additions and 164 deletions

View File

@ -136,12 +136,6 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
[ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001) [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
同时感谢JetBrains对开源项目的支持本项目使用IntelliJ IDEA开发与调试
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001 ![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/IntelliJ_IDEA_icon.svg?_ga=2.143694769.529214288.1712023294-439039083.1711422571&_gl=1*102dv9n*_ga*NDM5MDM5MDgzLjE3MTE0MjI1NzE.*_ga_9J976DJZ68*MTcxMjEyNjg4NC45LjEuMTcxMjEyNzc2My4zMy4wLjA.)
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000011_34020000001370000001
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000101_34020000001310000001

View File

@ -189,6 +189,9 @@ public class ParentPlatform {
@Schema(description = "是否作为消息通道") @Schema(description = "是否作为消息通道")
private boolean autoPushChannel; private boolean autoPushChannel;
@Schema(description = "点播回复200OK使用次IP")
private String sendStreamIp;
public Integer getId() { public Integer getId() {
return id; return id;
} }
@ -436,4 +439,12 @@ public class ParentPlatform {
public void setAutoPushChannel(boolean autoPushChannel) { public void setAutoPushChannel(boolean autoPushChannel) {
this.autoPushChannel = autoPushChannel; this.autoPushChannel = autoPushChannel;
} }
public String getSendStreamIp() {
return sendStreamIp;
}
public void setSendStreamIp(String sendStreamIp) {
this.sendStreamIp = sendStreamIp;
}
} }

View File

@ -305,4 +305,33 @@ public class SendRtpItem {
public void setReceiveStream(String receiveStream) { public void setReceiveStream(String receiveStream) {
this.receiveStream = receiveStream; this.receiveStream = receiveStream;
} }
@Override
public String toString() {
return "SendRtpItem{" +
"ip='" + ip + '\'' +
", port=" + port +
", ssrc='" + ssrc + '\'' +
", platformId='" + platformId + '\'' +
", deviceId='" + deviceId + '\'' +
", app='" + app + '\'' +
", channelId='" + channelId + '\'' +
", status=" + status +
", stream='" + stream + '\'' +
", tcp=" + tcp +
", tcpActive=" + tcpActive +
", localPort=" + localPort +
", mediaServerId='" + mediaServerId + '\'' +
", serverId='" + serverId + '\'' +
", CallId='" + CallId + '\'' +
", fromTag='" + fromTag + '\'' +
", toTag='" + toTag + '\'' +
", pt=" + pt +
", usePs=" + usePs +
", onlyAudio=" + onlyAudio +
", rtcp=" + rtcp +
", playType=" + playType +
", receiveStream='" + receiveStream + '\'' +
'}';
}
} }

View File

@ -116,7 +116,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
if (parentPlatform != null) { if (parentPlatform != null) {
Map<String, Object> param = getSendRtpParam(sendRtpItem); Map<String, Object> param = getSendRtpParam(sendRtpItem);
if (mediaInfo == null) { if (!userSetting.getServerId().equals(sendRtpItem.getServerId())) {
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(),
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),

View File

@ -38,6 +38,7 @@ import gov.nist.javax.sdp.fields.TimeField;
import gov.nist.javax.sdp.fields.URIField; import gov.nist.javax.sdp.fields.URIField;
import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -404,12 +405,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// * 2 推流中 // * 2 推流中
sendRtpItem.setStatus(1); sendRtpItem.setStatus(1);
redisCatchStorage.updateSendRTPSever(sendRtpItem); redisCatchStorage.updateSendRTPSever(sendRtpItem);
String sdpIp = mediaServerItemInUSe.getSdpIp();
if (!ObjectUtils.isEmpty(platform.getSendStreamIp())) {
sdpIp = platform.getSendStreamIp();
}
StringBuffer content = new StringBuffer(200); StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n"); content.append("v=0\r\n");
content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
content.append("s=" + sessionName + "\r\n"); content.append("s=" + sessionName + "\r\n");
content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n");
if ("Playback".equalsIgnoreCase(sessionName)) { if ("Playback".equalsIgnoreCase(sessionName)) {
content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n"); content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n");
} else { } else {
@ -575,15 +579,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} }
if ("push".equals(gbStream.getStreamType())) { if ("push".equals(gbStream.getStreamType())) {
if (streamPushItem != null && streamPushItem.isPushIng()) { if (streamPushItem != null) {
// 从redis查询是否正在接收这个推流
OnStreamChangedHookParam pushListItem = redisCatchStorage.getPushListItem(gbStream.getApp(), gbStream.getStream());
if (pushListItem != null) {
StreamPushItem transform = streamPushService.transform(pushListItem);
transform.setSelf(userSetting.getServerId().equals(pushListItem.getSeverId()));
// 推流状态 // 推流状态
pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, pushStream(evt, request, gbStream, transform, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
} else { }else {
// 未推流 拉起 // 未推流 拉起
notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
} }
}
} else if ("proxy".equals(gbStream.getStreamType())) { } else if ("proxy".equals(gbStream.getStreamType())) {
if (null != proxyByAppAndStream) { if (null != proxyByAppAndStream) {
if (proxyByAppAndStream.isStatus()) { if (proxyByAppAndStream.isStatus()) {
@ -901,11 +911,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
public SIPResponse sendStreamAck(MediaServerItem mediaServerItem, SIPRequest request, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) { public SIPResponse sendStreamAck(MediaServerItem mediaServerItem, SIPRequest request, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) {
String sdpIp = mediaServerItem.getSdpIp();
if (!ObjectUtils.isEmpty(platform.getSendStreamIp())) {
sdpIp = platform.getSendStreamIp();
}
StringBuffer content = new StringBuffer(200); StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n"); content.append("v=0\r\n");
content.append("o=" + sendRtpItem.getChannelId() + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); content.append("o=" + sendRtpItem.getChannelId() + " 0 0 IN IP4 " + sdpIp + "\r\n");
content.append("s=Play\r\n"); content.append("s=Play\r\n");
content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n");
content.append("t=0 0\r\n"); content.append("t=0 0\r\n");
// 非严格模式端口不统一, 增加兼容性修改为一个不为0的端口 // 非严格模式端口不统一, 增加兼容性修改为一个不为0的端口
int localPort = sendRtpItem.getLocalPort(); int localPort = sendRtpItem.getLocalPort();

View File

@ -509,7 +509,19 @@ public class ZLMHttpHookListener {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
if (!sendRtpItems.isEmpty()) { if (!sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) { for (SendRtpItem sendRtpItem : sendRtpItems) {
if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) { if (sendRtpItem == null) {
continue;
}
if (sendRtpItem.getApp().equals(param.getApp())) {
logger.info(sendRtpItem.toString());
if (userSetting.getServerId().equals(sendRtpItem.getServerId())) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), null, userSetting.getServerId(), param.getMediaServerId());
// 通知其他wvp停止发流
redisCatchStorage.sendPushStreamClose(messageForPushChannel);
}else {
String platformId = sendRtpItem.getPlatformId(); String platformId = sendRtpItem.getPlatformId();
ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
Device device = deviceService.getDevice(platformId); Device device = deviceService.getDevice(platformId);
@ -536,6 +548,8 @@ public class ZLMHttpHookListener {
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
} }
} }
}
} }
} }
} }

View File

@ -98,4 +98,5 @@ public interface IDeviceChannelService {
void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition); void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition);
void stopPlay(String deviceId, String channelId);
} }

View File

@ -68,4 +68,5 @@ public interface IPlayService {
void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
void stopPlay(Device device, String channelId);
} }

View File

@ -353,4 +353,9 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
redisCatchStorage.sendMobilePositionMsg(jsonObject); redisCatchStorage.sendMobilePositionMsg(jsonObject);
} }
} }
@Override
public void stopPlay(String deviceId, String channelId) {
channelMapper.stopPlay(deviceId, channelId);
}
} }

View File

@ -34,7 +34,6 @@ import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
import com.genersoft.iot.vmp.utils.CloudRecordUtils; import com.genersoft.iot.vmp.utils.CloudRecordUtils;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
@ -123,9 +122,6 @@ public class PlayServiceImpl implements IPlayService {
@Autowired @Autowired
private DynamicTask dynamicTask; private DynamicTask dynamicTask;
@Autowired
private CloudRecordServiceMapper cloudRecordServiceMapper;
@Autowired @Autowired
private ISIPCommanderForPlatform commanderForPlatform; private ISIPCommanderForPlatform commanderForPlatform;
@ -1170,7 +1166,7 @@ public class PlayServiceImpl implements IPlayService {
dynamicTask.startDelay(key, ()->{ dynamicTask.startDelay(key, ()->{
logger.info("[语音广播]等待invite消息超时{}/{}", device.getDeviceId(), channelId); logger.info("[语音广播]等待invite消息超时{}/{}", device.getDeviceId(), channelId);
stopAudioBroadcast(device.getDeviceId(), channelId); stopAudioBroadcast(device.getDeviceId(), channelId);
}, 2000); }, 10*1000);
}, eventResultForError -> { }, eventResultForError -> {
// 发送失败 // 发送失败
logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg);
@ -1592,4 +1588,26 @@ public class PlayServiceImpl implements IPlayService {
}); });
} }
@Override
public void stopPlay(Device device, String channelId) {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
if (inviteInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到");
}
if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
try {
logger.info("[停止点播] {}/{}", device.getDeviceId(), channelId);
cmder.streamByeCmd(device, channelId, inviteInfo.getStream(), null, null);
} catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
}
}
inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
storager.stopPlay(device.getDeviceId(), channelId);
channelService.stopPlay(device.getDeviceId(), channelId);
if (inviteInfo.getStreamInfo() != null) {
mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
}
}
} }

View File

@ -8,7 +8,6 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
@ -25,7 +24,6 @@ import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper; import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper; import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
@ -333,8 +331,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(), result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(),
param.isEnableAudio(), param.isEnableMp4(), param.getRtpType()); param.isEnableAudio(), param.isEnableMp4(), param.getRtpType());
} }
System.out.println("addStreamProxyToZlm====");
System.out.println(result);
if (result != null && result.getInteger("code") == 0) { if (result != null && result.getInteger("code") == 0) {
JSONObject data = result.getJSONObject("data"); JSONObject data = result.getJSONObject("data");
if (data == null) { if (data == null) {

View File

@ -2,12 +2,10 @@ package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamPushService; import com.genersoft.iot.vmp.service.IStreamPushService;
@ -25,7 +23,6 @@ import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.SipException; import javax.sip.SipException;
import java.text.ParseException; import java.text.ParseException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -86,26 +83,6 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
} }
} }
if (push.isSelf()) {
// 停止向上级推流
String streamId = sendRtpItem.getStream();
Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__");
param.put("app",sendRtpItem.getApp());
param.put("stream",streamId);
param.put("ssrc",sendRtpItem.getSsrc());
logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}
}
} }
} }
} }

View File

@ -1,11 +1,7 @@
package com.genersoft.iot.vmp.service.redisMsg; package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager; import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -41,52 +37,52 @@ public class RedisStreamMsgListener implements MessageListener {
@Override @Override
public void onMessage(Message message, byte[] bytes) { public void onMessage(Message message, byte[] bytes) {
boolean isEmpty = taskQueue.isEmpty(); // boolean isEmpty = taskQueue.isEmpty();
taskQueue.offer(message); // taskQueue.offer(message);
if (isEmpty) { // if (isEmpty) {
taskExecutor.execute(() -> { // taskExecutor.execute(() -> {
while (!taskQueue.isEmpty()) { // while (!taskQueue.isEmpty()) {
Message msg = taskQueue.poll(); // Message msg = taskQueue.poll();
try { // try {
JSONObject steamMsgJson = JSON.parseObject(msg.getBody(), JSONObject.class); // JSONObject steamMsgJson = JSON.parseObject(msg.getBody(), JSONObject.class);
if (steamMsgJson == null) { // if (steamMsgJson == null) {
logger.warn("[收到redis 流变化]消息解析失败"); // logger.warn("[收到redis 流变化]消息解析失败");
continue; // continue;
} // }
String serverId = steamMsgJson.getString("serverId"); // String serverId = steamMsgJson.getString("serverId");
//
if (userSetting.getServerId().equals(serverId)) { // if (userSetting.getServerId().equals(serverId)) {
// 自己发送的消息忽略即可 // // 自己发送的消息忽略即可
continue; // continue;
} // }
logger.info("[收到redis 流变化] {}", new String(message.getBody())); // logger.info("[收到redis 流变化] {}", new String(message.getBody()));
String app = steamMsgJson.getString("app"); // String app = steamMsgJson.getString("app");
String stream = steamMsgJson.getString("stream"); // String stream = steamMsgJson.getString("stream");
boolean register = steamMsgJson.getBoolean("register"); // boolean register = steamMsgJson.getBoolean("register");
String mediaServerId = steamMsgJson.getString("mediaServerId"); // String mediaServerId = steamMsgJson.getString("mediaServerId");
OnStreamChangedHookParam onStreamChangedHookParam = new OnStreamChangedHookParam(); // OnStreamChangedHookParam onStreamChangedHookParam = new OnStreamChangedHookParam();
onStreamChangedHookParam.setSeverId(serverId); // onStreamChangedHookParam.setSeverId(serverId);
onStreamChangedHookParam.setApp(app); // onStreamChangedHookParam.setApp(app);
onStreamChangedHookParam.setStream(stream); // onStreamChangedHookParam.setStream(stream);
onStreamChangedHookParam.setRegist(register); // onStreamChangedHookParam.setRegist(register);
onStreamChangedHookParam.setMediaServerId(mediaServerId); // onStreamChangedHookParam.setMediaServerId(mediaServerId);
onStreamChangedHookParam.setCreateStamp(System.currentTimeMillis()/1000); // onStreamChangedHookParam.setCreateStamp(System.currentTimeMillis()/1000);
onStreamChangedHookParam.setAliveSecond(0L); // onStreamChangedHookParam.setAliveSecond(0L);
onStreamChangedHookParam.setTotalReaderCount("0"); // onStreamChangedHookParam.setTotalReaderCount("0");
onStreamChangedHookParam.setOriginType(0); // onStreamChangedHookParam.setOriginType(0);
onStreamChangedHookParam.setOriginTypeStr("0"); // onStreamChangedHookParam.setOriginTypeStr("0");
onStreamChangedHookParam.setOriginTypeStr("unknown"); // onStreamChangedHookParam.setOriginTypeStr("unknown");
if (register) { // if (register) {
zlmMediaListManager.addPush(onStreamChangedHookParam); // zlmMediaListManager.addPush(onStreamChangedHookParam);
}else { // }else {
zlmMediaListManager.removeMedia(app, stream); // zlmMediaListManager.removeMedia(app, stream);
} // }
}catch (Exception e) { // }catch (Exception e) {
logger.warn("[REDIS消息-流变化] 发现未处理的异常, \r\n{}", JSON.toJSONString(message)); // logger.warn("[REDIS消息-流变化] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
logger.error("[REDIS消息-流变化] 异常内容: ", e); // logger.error("[REDIS消息-流变化] 异常内容: ", e);
} // }
} // }
}); // });
} // }
} }
} }

View File

@ -211,5 +211,10 @@ public interface IRedisCatchStorage {
void addPushListItem(String app, String stream, OnStreamChangedHookParam param); void addPushListItem(String app, String stream, OnStreamChangedHookParam param);
OnStreamChangedHookParam getPushListItem(String app, String stream);
void removePushListItem(String app, String stream, String mediaServerId); void removePushListItem(String app, String stream, String mediaServerId);
void sendPushStreamClose(MessageForPushChannel messageForPushChannel);
} }

View File

@ -17,10 +17,10 @@ public interface ParentPlatformMapper {
@Insert("INSERT INTO wvp_platform (enable, name, server_gb_id, server_gb_domain, server_ip, server_port,device_gb_id,device_ip,"+ @Insert("INSERT INTO wvp_platform (enable, name, server_gb_id, server_gb_domain, server_ip, server_port,device_gb_id,device_ip,"+
"device_port,username,password,expires,keep_timeout,transport,character_set,ptz,rtcp,as_message_channel,auto_push_channel,"+ "device_port,username,password,expires,keep_timeout,transport,character_set,ptz,rtcp,as_message_channel,auto_push_channel,"+
"status,start_offline_push,catalog_id,administrative_division,catalog_group,create_time,update_time) " + "status,start_offline_push,catalog_id,administrative_division,catalog_group,create_time,update_time,send_stream_ip) " +
" VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " + " VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " +
" #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, #{autoPushChannel}, " + " #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, #{autoPushChannel}, " +
" #{status}, #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime})") " #{status}, #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime}, #{sendStreamIp})")
int addParentPlatform(ParentPlatform parentPlatform); int addParentPlatform(ParentPlatform parentPlatform);
@Update("UPDATE wvp_platform " + @Update("UPDATE wvp_platform " +
@ -49,6 +49,7 @@ public interface ParentPlatformMapper {
"administrative_division=#{administrativeDivision}, " + "administrative_division=#{administrativeDivision}, " +
"create_time=#{createTime}, " + "create_time=#{createTime}, " +
"update_time=#{updateTime}, " + "update_time=#{updateTime}, " +
"send_stream_ip=#{sendStreamIp}, " +
"catalog_id=#{catalogId} " + "catalog_id=#{catalogId} " +
"WHERE id=#{id}") "WHERE id=#{id}")
int updateParentPlatform(ParentPlatform parentPlatform); int updateParentPlatform(ParentPlatform parentPlatform);

View File

@ -656,6 +656,12 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
redisTemplate.opsForValue().set(key, param); redisTemplate.opsForValue().set(key, param);
} }
@Override
public OnStreamChangedHookParam getPushListItem(String app, String stream) {
String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream;
return (OnStreamChangedHookParam)redisTemplate.opsForValue().get(key);
}
@Override @Override
public void removePushListItem(String app, String stream, String mediaServerId) { public void removePushListItem(String app, String stream, String mediaServerId) {
String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream;
@ -665,4 +671,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
} }
} }
@Override
public void sendPushStreamClose(MessageForPushChannel msg) {
String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED;
logger.info("[redis发送通知] 发送 停止向上级推流 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId());
redisTemplate.convertAndSend(key, JSON.toJSON(msg));
}
} }

View File

@ -1,12 +1,8 @@
package com.genersoft.iot.vmp.vmanager.cloudRecord; package com.genersoft.iot.vmp.vmanager.cloudRecord;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.media.zlm.SendRtpPortManager;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.ICloudRecordService; import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
@ -22,7 +18,6 @@ import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;

View File

@ -3,12 +3,10 @@ package com.genersoft.iot.vmp.vmanager.gb28181.play;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.InviteSessionStatus;
import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
@ -26,7 +24,7 @@ import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.*; import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@ -41,11 +39,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.text.ParseException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -157,7 +152,8 @@ public class PlayController {
wvpResult.setMsg(msg); wvpResult.setMsg(msg);
} }
requestMessage.setData(wvpResult); requestMessage.setData(wvpResult);
resultHolder.invokeResult(requestMessage); // 此处必须释放所有请求
resultHolder.invokeAllResult(requestMessage);
}); });
return result; return result;
} }
@ -165,9 +161,8 @@ public class PlayController {
@Operation(summary = "停止点播", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "停止点播", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号", required = true) @Parameter(name = "channelId", description = "通道国标编号", required = true)
@Parameter(name = "isSubStream", description = "是否子码流true-子码流false-主码流默认为false", required = true)
@GetMapping("/stop/{deviceId}/{channelId}") @GetMapping("/stop/{deviceId}/{channelId}")
public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId,boolean isSubStream) { public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId) {
logger.debug(String.format("设备预览/回放停止API调用streamId%s_%s", deviceId, channelId )); logger.debug(String.format("设备预览/回放停止API调用streamId%s_%s", deviceId, channelId ));
@ -180,26 +175,10 @@ public class PlayController {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在");
} }
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); playService.stopPlay(device, channelId);
if (inviteInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到");
}
if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
try {
logger.info("[停止点播] {}/{}", device.getDeviceId(), channelId);
cmder.streamByeCmd(device, channelId, inviteInfo.getStream(), null, null);
} catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
}
}
inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
storager.stopPlay(deviceId, channelId);
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put("deviceId", deviceId); json.put("deviceId", deviceId);
json.put("channelId", channelId); json.put("channelId", channelId);
json.put("isSubStream", isSubStream);
return json; return json;
} }

View File

@ -37,8 +37,8 @@
<el-form-item label="本地端口" prop="devicePort"> <el-form-item label="本地端口" prop="devicePort">
<el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input> <el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="SIP认证用户名" prop="username"> <el-form-item label="SDP发流IP" prop="sendStreamIp">
<el-input v-model="platform.username"></el-input> <el-input v-model="platform.sendStreamIp"></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-col> </el-col>
@ -47,6 +47,9 @@
<el-form-item label="行政区划" prop="administrativeDivision"> <el-form-item label="行政区划" prop="administrativeDivision">
<el-input v-model="platform.administrativeDivision" clearable></el-input> <el-input v-model="platform.administrativeDivision" clearable></el-input>
</el-form-item> </el-form-item>
<el-form-item label="SIP认证用户名" prop="username">
<el-input v-model="platform.username"></el-input>
</el-form-item>
<el-form-item label="SIP认证密码" prop="password"> <el-form-item label="SIP认证密码" prop="password">
<el-input v-model="platform.password" ></el-input> <el-input v-model="platform.password" ></el-input>
</el-form-item> </el-form-item>
@ -159,7 +162,8 @@ export default {
characterSet: "GB2312", characterSet: "GB2312",
startOfflinePush: false, startOfflinePush: false,
catalogGroup: 1, catalogGroup: 1,
administrativeDivision: null, administrativeDivision: "",
sendStreamIp: null,
}, },
rules: { rules: {
name: [{ required: true, message: "请输入平台名称", trigger: "blur" }], name: [{ required: true, message: "请输入平台名称", trigger: "blur" }],
@ -198,6 +202,7 @@ export default {
that.platform.devicePort = res.data.data.devicePort; that.platform.devicePort = res.data.data.devicePort;
that.platform.username = res.data.data.username; that.platform.username = res.data.data.username;
that.platform.password = res.data.data.password; that.platform.password = res.data.data.password;
that.platform.sendStreamIp = res.data.data.sendStreamIp;
that.platform.administrativeDivision = res.data.data.username.substr(0, 6); that.platform.administrativeDivision = res.data.data.username.substr(0, 6);
} }
@ -228,6 +233,7 @@ export default {
this.platform.catalogId = platform.catalogId; this.platform.catalogId = platform.catalogId;
this.platform.startOfflinePush = platform.startOfflinePush; this.platform.startOfflinePush = platform.startOfflinePush;
this.platform.catalogGroup = platform.catalogGroup; this.platform.catalogGroup = platform.catalogGroup;
this.platform.sendStreamIp = platform.sendStreamIp;
this.platform.administrativeDivision = platform.administrativeDivision; this.platform.administrativeDivision = platform.administrativeDivision;
this.onSubmit_text = "保存"; this.onSubmit_text = "保存";
this.saveUrl = "/api/platform/save"; this.saveUrl = "/api/platform/save";

View File

@ -198,6 +198,7 @@ create table wvp_platform (
update_time character varying(50), update_time character varying(50),
as_message_channel bool default false, as_message_channel bool default false,
auto_push_channel bool default false, auto_push_channel bool default false,
send_stream_ip character varying(50),
constraint uk_platform_unique_server_gb_id unique (server_gb_id) constraint uk_platform_unique_server_gb_id unique (server_gb_id)
); );

View File

@ -198,6 +198,7 @@ create table wvp_platform (
update_time character varying(50), update_time character varying(50),
as_message_channel bool default false, as_message_channel bool default false,
auto_push_channel bool default false, auto_push_channel bool default false,
send_stream_ip character varying(50),
constraint uk_platform_unique_server_gb_id unique (server_gb_id) constraint uk_platform_unique_server_gb_id unique (server_gb_id)
); );

View File

@ -3,3 +3,6 @@ alter table wvp_device_channel
alter table wvp_device alter table wvp_device
drop switch_primary_sub_stream; drop switch_primary_sub_stream;
alter table wvp_platform
add send_stream_ip character varying(50);

View File

@ -3,3 +3,6 @@ alter table wvp_device_channel
alter table wvp_device alter table wvp_device
drop switch_primary_sub_stream; drop switch_primary_sub_stream;
alter table wvp_platform
add send_stream_ip character varying(50);