kindring 2 years ago
parent
commit
f32e3ee3a9
24 changed files with 440 additions and 117 deletions
  1. 2 0
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  2. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  3. 0 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  4. 30 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  5. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  6. 3 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
  7. 4 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  8. 48 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  9. 5 0
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  10. 2 0
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  11. 5 0
      src/main/java/com/genersoft/iot/vmp/service/bean/NodeCallBack.java
  12. 52 0
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  13. 161 5
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  14. 2 1
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  15. 12 1
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  16. 6 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  17. 28 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  18. 8 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  19. 0 34
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/broadCast/BroadCast.java
  20. 48 59
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  21. 3 3
      src/main/resources/application.yml
  22. 7 5
      web_src/config/index.js
  23. 2 2
      web_src/src/components/channelList.vue
  24. 10 1
      web_src/src/components/common/microphone.vue

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -14,6 +14,8 @@ public class VideoManagerConstants {
 
 	public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
 
+	public static final String MEDIA_SERVER_BROADCAST_PREFIX = "VMP_MEDIA_BROADCAST_SERVER_";
+
 	public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
 
 	public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@@ -54,6 +54,7 @@ public class DeferredResultHolder {
 	public static final String CALLBACK_CMD_QUERY_AI = "CALLBACK_CMD_QUERY_AI";
 	public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
 
+	public static final String CALLBACK_CMD_BROADCAST_INVITE = "CALLBACK_BROADCAST_INVITE";
 	private Map<String, Map<String, DeferredResult>> map = new ConcurrentHashMap<>();
 
 

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -170,7 +170,6 @@ public interface ISIPCommander {
 	 * 语音广播
 	 * 
 	 * @param device  视频设备
-	 * @param channelId  预览通道
 	 */
 	void audioBroadcastCmd(Device device,String channelId);
 	

+ 30 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
@@ -31,6 +32,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import javax.sip.*;
 import javax.sip.header.*;
@@ -675,6 +677,34 @@ public class SIPCommander implements ISIPCommander {
 
     }
 
+    public void startAudioBroadcastCmd(MediaServerItem mediaServerItem,
+                                       SSRCInfo ssrcInfo,
+                                       Device device,
+                                       ZlmHttpHookSubscribe.Event event,
+                                       SipSubscribe.Event okEvent,
+                                       SipSubscribe.Event errorEvent)throws InvalidArgumentException, SipException, ParseException
+    {
+        String stream = ssrcInfo.getStream();
+        if (device == null) {
+            return;
+        }
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        // 创建broadcast 触发器
+        DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
+        String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
+        // 下发broadcast命令
+        StringBuffer broadcastXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        broadcastXml.append("<Notify>\r\n");
+        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
+        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
+        broadcastXml.append("</Notify>\r\n");
+
+    }
+
     @Override
     public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -846,7 +846,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             String username = sdp.getOrigin().getUsername();
             String addressStr = sdp.getOrigin().getAddress();
             logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
-            // todo 向zlm服务器申请推流转发通道
+            // todo 向zlm服务器申请语音推流转发通道
         } else {
             logger.warn("来自无效设备/平台的请求");
             responseAck(serverTransaction, Response.BAD_REQUEST);

+ 3 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java

@@ -45,10 +45,10 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
         try {
-            String channelId = getText(rootElement, "DeviceID");
+
             logger.info("[sip received] broadcast response from {}",device.getDeviceId());
-            // fix 修改key 取消channelId的key拼接
-            String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
+            // invite 信息完成
+            String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST_INVITE + device.getDeviceId();
 
             ServerTransaction serverTransaction = getServerTransaction(evt);
             // 回复200 OK
@@ -64,7 +64,6 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
             msg.setData(json);
             deferredResultHolder.invokeAllResult(msg);
 
-
         } catch (ParseException | SipException | InvalidArgumentException e) {
             logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
         }

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -284,6 +284,10 @@ public class ZLMRESTfulUtils {
         return sendPost(mediaServerItem, "stopSendRtp",param, null);
     }
 
+    public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object> param){
+        return sendPost(mediaServerItem,"startSendRtpPassive" , param ,null);
+    }
+
     public JSONObject restartServer(MediaServerItem mediaServerItem) {
         return sendPost(mediaServerItem, "restartServer",null, null);
     }

+ 48 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.media.zlm;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.conf.UserSetting;
@@ -275,6 +276,43 @@ public class ZLMRTPServerFactory {
         return sendRtpItem;
     }
 
+    /**
+     * 创建音频广播rtp推流通道,被动推流
+     * @param serverItem
+     * @param streamId
+     * @param ssrc
+     * @return
+     */
+    public Map<String, Object> createStartSendRtpStreamPassiveData(MediaServerItem serverItem,String streamId,String ssrc){
+        String sendRtpPortRange = serverItem.getSendRtpPortRange();
+        if (ObjectUtils.isEmpty(sendRtpPortRange)) {
+            return null;
+        }
+        String[] portRangeStrArray = serverItem.getSendRtpPortRange().split(",");
+        int localPort = -1;
+        if (portRangeStrArray.length != 2) {
+            localPort = getFreePort(serverItem, 30000, 30500, null);
+        }else {
+            localPort = getFreePort(serverItem, Integer.parseInt(portRangeStrArray[0]),  Integer.parseInt(portRangeStrArray[1]), null);
+        }
+        if (localPort == -1) {
+            logger.error("没有可用的端口");
+            return null;
+        }
+
+        Map<String, Object> param = new HashMap<>();
+        param.put("enable_tcp", 1);
+
+        // 推流端口设置0则使用随机端口
+        param.put("port", 0);
+        param.put("use_ps", 0);
+        param.put("only_audio", 1);
+        param.put("ssrc", ssrc);
+        param.put("app", "broadcast");
+        param.put("stream", streamId);
+//        JSONObject itemJSONObj = JSONObject.parseObject(JSON.toJSONString(param));
+        return param;
+    }
     /**
      * 调用zlm RESTFUL API —— startSendRtp
      */
@@ -282,6 +320,16 @@ public class ZLMRTPServerFactory {
         return zlmresTfulUtils.startSendRtp(mediaServerItem, param);
     }
 
+    /**
+     * 调用zlm RESTFUL API --- startSendRtpPassive
+     * @param mediaServerItem
+     * @param param
+     * @return
+     */
+    public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param) {
+        return zlmresTfulUtils.startSendRtp(mediaServerItem, param);
+    }
+
     /**
      * 查询待转推的流是否就绪
      */

+ 5 - 0
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java

@@ -50,6 +50,11 @@ public interface IMediaServerService {
 
     SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port);
 
+
+    SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId,boolean ssrcCheck, boolean isPlayback);
+    SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port);
+    SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback);
+
     void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
 
     void closeRTPServer(String mediaServerId, String streamId);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -32,6 +32,8 @@ public interface IPlayService {
               InviteTimeOutCallback timeoutCallback, String uuid);
     PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent, Runnable timeoutCallback);
 
+    public PlayResult openBroadcast(MediaServerItem mediaServerItem,String deviceId,ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
+                              Runnable timeoutCallback);
     MediaServerItem getNewMediaServerItem(Device device);
 
     void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String toString);

+ 5 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/NodeCallBack.java

@@ -0,0 +1,5 @@
+package com.genersoft.iot.vmp.service.bean;
+
+public interface NodeCallBack {
+    void run(int code, String msg);
+}

+ 52 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -10,6 +10,7 @@ import java.util.Set;
 
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -120,6 +121,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
         return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback);
     }
 
+
+
     @Override
     public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) {
         if (mediaServerItem == null || mediaServerItem.getId() == null) {
@@ -163,6 +166,55 @@ public class MediaServerServiceImpl implements IMediaServerService {
         return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, null);
     }
 
+
+    @Override
+    public SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) {
+        return startSendRtpServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback);
+    }
+
+    @Override
+    public SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) {
+        if (mediaServerItem == null || mediaServerItem.getId() == null) {
+            return null;
+        }
+        // 获取mediaServer可用的ssrc
+        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId();
+
+        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
+        if (ssrcConfig == null) {
+            logger.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
+            return null;
+        }else {
+            String ssrc;
+            if (presetSsrc != null) {
+                ssrc = presetSsrc;
+            }else {
+                if (isPlayback) {
+                    ssrc = ssrcConfig.getPlayBackSsrc();
+                }else {
+                    ssrc = ssrcConfig.getPlaySsrc();
+                }
+            }
+
+            if (streamId == null) {
+                streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
+            }
+            int rtpServerPort;
+            // 创建
+            Map<String, Object> rtpParam = zlmrtpServerFactory.createStartSendRtpStreamPassiveData(mediaServerItem, streamId,ssrc );
+            JSONObject result = zlmrtpServerFactory.startSendRtpPassive(mediaServerItem,rtpParam);
+            RedisUtil.set(key, mediaServerItem);
+            rtpServerPort = (int) result.get("local_port");
+            return new SSRCInfo(rtpServerPort, ssrc, streamId);
+        }
+    }
+
+    @Override
+    public SSRCInfo startSendRtpServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback) {
+        return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, null);
+    }
+
+
     @Override
     public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
         if (mediaServerItem == null) {

+ 161 - 5
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -15,6 +15,7 @@ import com.genersoft.iot.vmp.conf.exception.ServiceException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.service.bean.*;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import org.slf4j.Logger;
@@ -47,10 +48,6 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IMediaService;
 import com.genersoft.iot.vmp.service.IPlayService;
-import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
-import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
-import com.genersoft.iot.vmp.service.bean.PlayBackResult;
-import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -113,7 +110,7 @@ public class PlayServiceImpl implements IPlayService {
     private ThreadPoolTaskExecutor taskExecutor;
 
 
-
+    // todo 模仿play接口开启 rtp 推流接口
     @Override
     public PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId,
                            ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
@@ -371,6 +368,165 @@ public class PlayServiceImpl implements IPlayService {
         }
     }
 
+
+    public PlayResult openBroadcast(MediaServerItem mediaServerItem,String deviceId,ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
+                              Runnable timeoutCallback){
+
+        PlayResult playResult = new PlayResult();
+        PlayResult playResult_invite = new PlayResult();
+        RequestMessage msg = new RequestMessage();
+        RequestMessage msg_invite = new RequestMessage();
+        String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
+        // 使用invite 来进行延迟
+        String key_invite  = DeferredResultHolder.CALLBACK_CMD_BROADCAST_INVITE + deviceId;
+        msg.setKey(key);
+        msg_invite.setKey(key_invite);
+        String uuid = UUID.randomUUID().toString();
+        String uuid_invite = UUID.randomUUID().toString();
+        msg.setId(uuid);
+        msg_invite.setId(uuid_invite);
+        playResult.setUuid(uuid);
+        playResult_invite.setUuid(uuid_invite);
+        DeferredResult<WVPResult<String>> result = new DeferredResult<>(13*1000l);
+        playResult.setResult(result);
+        resultHolder.put(key, uuid, result);
+        DeferredResult<WVPResult<String>> result_invite = new DeferredResult<>(17*1000l);
+        playResult.setResult(result_invite);
+        resultHolder.put(key_invite, uuid_invite, result_invite);
+        // 检查zlm
+        if (mediaServerItem == null) {
+            logger.warn("[语音广播] 无法连接至ZLM服务器");
+            WVPResult wvpResult = new WVPResult();
+            wvpResult.setCode(ErrorCode.ERROR100.getCode());
+            wvpResult.setMsg("无法连接至流媒体服务器");
+            msg.setData(wvpResult);
+            resultHolder.invokeAllResult(msg);
+            return playResult;
+        }
+
+        // 此段为录像查询...
+        Device device = redisCatchStorage.getDevice(deviceId);
+        StreamInfo streamInfo = redisCatchStorage.queryRecordByDevice(deviceId);
+        playResult.setDevice(device);
+        result.onCompletion(()->{
+            // 通道结束
+            logger.info("[语音广播] 结束....");
+            // TODO: 2023/3/7 开始下发invite信息给设备
+//            下发invite信息给设备
+        });
+        result_invite.onCompletion(()->{
+            logger.info("[语音广播] 接收到设备invite信息_____");
+            resultHolder.invokeAllResult(msg);
+            WVPResult wvpResult = new WVPResult();
+            wvpResult.setCode(ErrorCode.SUCCESS.getCode());
+            wvpResult.setMsg("okokokokooo");
+            msg.setData(wvpResult);
+            resultHolder.invokeAllResult(msg);
+            // TODO: 2023/3/7 开始下发invite信息给设备
+        });
+        result.onTimeout(()->{
+            logger.warn("[广播超时] 与设备交互broadcast流程超时,未收到设备invite信息");
+            WVPResult wvpResult = new WVPResult();
+            wvpResult.setCode(ErrorCode.ERROR100.getCode());
+            wvpResult.setMsg("[广播超时] 与设备交互broadcast流程超时,未收到设备invite信息");
+            msg.setData(wvpResult);
+            resultHolder.invokeAllResult(msg);
+        });
+
+
+        // 检查无法连接zlm的情况
+        if (streamInfo != null) {
+            String streamId = streamInfo.getStream();
+            if (streamId == null){
+                logger.warn("[语音广播] zlm 缓存的 streamId等于null");
+                WVPResult wvpResult = new WVPResult();
+                wvpResult.setCode(ErrorCode.ERROR100.getCode());
+                wvpResult.setMsg("zlm 缓存的 streamId等于null");
+                msg.setData(wvpResult);
+                resultHolder.invokeAllResult(msg);
+                return playResult;
+            }
+            String mediaServerId = streamInfo.getMediaServerId();
+            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
+            if(rtpInfo.getInteger("code") == 0){
+                if (rtpInfo.getBoolean("exist")) {
+                    int localPort = rtpInfo.getInteger("local_port");
+                    if (localPort == 0) {
+                        logger.warn("[语音广播],点播时发现rtpServerC存在,但是尚未开始推流");
+                        // 此时说明rtpServer已经创建但是流还没有推上来
+                        WVPResult wvpResult = new WVPResult();
+                        wvpResult.setCode(ErrorCode.ERROR100.getCode());
+                        wvpResult.setMsg("语音广播已经开始");
+                        msg.setData(wvpResult);
+
+                        resultHolder.invokeAllResult(msg);
+                        return playResult;
+                    }else{
+                        // 语音点播已经开始,返回推流信息给前端
+                        WVPResult wvpResult = new WVPResult();
+                        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
+                        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
+                        wvpResult.setData(streamInfo);
+                        msg.setData(wvpResult);
+                        resultHolder.invokeAllResult(msg);
+                        if (hookEvent != null) {
+                            hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                        }
+                    }
+                }else{
+                    logger.warn("[语音广播] 停止语音广播推流转发端口");
+                    redisCatchStorage.stopBroadcast(streamInfo);
+                    storager.stopBroadcast(streamInfo.getDeviceID());
+                    streamInfo = null;
+                }
+            }else{
+                logger.warn("[语音广播] 无法连接至zlm服务器");
+                redisCatchStorage.stopPlay(streamInfo);
+                storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
+                streamInfo = null;
+            }
+        }
+
+        if(streamInfo == null){
+            String streamId = null;
+            if (mediaServerItem.isRtpEnable()) {
+                streamId = String.format("broadcast_%s", device.getDeviceId());
+            }
+            // 开始创建 rtp/tcp 推流通道
+            logger.info("[语音广播] 尝试创建rtp语音推流通道");
+            SSRCInfo ssrcInfo = mediaServerService.startSendRtpServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
+            logger.info(JSONObject.toJSONString(ssrcInfo));
+            // rtp语音通道创建完成,开始发送broadcast
+            broadcastEventHandle(device,
+                (int code,String tipMsg)->{
+                    if(code == 1){
+                        WVPResult wvpResult = new WVPResult();
+                        wvpResult.setCode(ErrorCode.ERROR100.getCode());
+                        wvpResult.setMsg(tipMsg);
+                        msg.setData(wvpResult);
+                        // 回复之前所有的点播请求
+                        resultHolder.invokeAllResult(msg);
+                    }
+                }
+            );
+        }
+        return playResult;
+    }
+
+    public void broadcastEventHandle(
+            Device device,
+            NodeCallBack nodeCallBack){
+        logger.info("[语音广播] 开始broadcast交互");
+        try {
+            cmder.audioBroadcastCmd(device);
+            nodeCallBack.run(0,"ok");
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 发送broadcast中 errorMsg: {}", e.getMessage());
+            nodeCallBack.run(1,"[命令发送失败] 无法发送broadcast消息");
+        }
+  }
+
     @Override
     public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String uuid) {
         RequestMessage msg = new RequestMessage();

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@@ -38,6 +38,7 @@ public interface IRedisCatchStorage {
      */
     boolean stopPlay(StreamInfo streamInfo);
 
+    boolean stopBroadcast(StreamInfo streamInfo);
     /**
      * 查询播放列表
      * @return
@@ -47,7 +48,7 @@ public interface IRedisCatchStorage {
     StreamInfo queryPlayByStreamId(String steamId);
 
     StreamInfo queryPlayByDevice(String deviceId, String channelId);
-
+    StreamInfo queryRecordByDevice(String deviceId);
     Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
 
     boolean startPlayback(StreamInfo stream, String callId);

+ 12 - 1
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -43,7 +43,18 @@ public interface IVideoManagerStorage {
 	 * @param channelId 通道ID
 	 */
 	public void stopPlay(String deviceId, String channelId);
-	
+
+	/**
+	 * 语音推流通道存储
+	 * @param deviceId
+	 * @param streamId
+	 */
+	public void startBroadcast(String deviceId, String streamId);
+	/**
+	 * 语音推流通道删除
+	 * @param deviceId
+	 */
+	public void stopBroadcast(String deviceId);
 	/**   
 	 * 获取设备
 	 * 

+ 6 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -92,9 +92,15 @@ public interface DeviceChannelMapper {
     @Update(value = {"UPDATE device_channel SET streamId=null WHERE deviceId=#{deviceId} AND channelId=#{channelId}"})
     void stopPlay(String deviceId, String channelId);
 
+    @Update(value = {"UPDATE device SET broadcastId=null WHERE deviceId=#{deviceId}"})
+    void stopBroadcast(String deviceId);
+
     @Update(value = {"UPDATE device_channel SET streamId=#{streamId} WHERE deviceId=#{deviceId} AND channelId=#{channelId}"})
     void startPlay(String deviceId, String channelId, String streamId);
 
+    @Update(value = {"UPDATE device SET streamId=#{streamId} WHERE deviceId=#{deviceId}"})
+    void startBroadcast(String deviceId, String streamId);
+
     @Select(value = {" <script>" +
             "SELECT " +
             "    dc.id,\n" +

+ 28 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -110,6 +110,23 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
                 streamInfo.getChannelId()));
     }
 
+
+    /**
+     * 从redis 中清除 broadcast 缓存信息
+     * @param streamInfo
+     * @return
+     */
+    public boolean stopBroadcast(StreamInfo streamInfo) {
+        if (streamInfo == null) {
+            return false;
+        }
+        return RedisUtil.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
+                userSetting.getServerId(),
+                streamInfo.getStream(),
+                streamInfo.getDeviceID()
+                ));
+    }
+
     /**
      * 查询播放列表
      * @return
@@ -145,6 +162,17 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         return (StreamInfo)RedisUtil.get(playLeys.get(0).toString());
     }
 
+    public StreamInfo queryRecordByDevice(String deviceId){
+        List<Object> playLeys = RedisUtil.scan(String.format("%S_%s_*_%s", VideoManagerConstants.PLAYER_PREFIX,
+                userSetting.getServerId(),
+                deviceId
+                ));
+        if (playLeys == null || playLeys.size() == 0) {
+            return null;
+        }
+        return (StreamInfo)RedisUtil.get(playLeys.get(0).toString());
+    }
+
     @Override
     public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
         Map<String, StreamInfo> streamInfos = new HashMap<>();

+ 8 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -224,6 +224,14 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		deviceChannelMapper.stopPlay(deviceId, channelId);
 	}
 
+	public void startBroadcast(String deviceId, String streamId) {
+		deviceChannelMapper.startBroadcast(deviceId, streamId);
+	}
+
+	public void stopBroadcast(String deviceId) {
+		deviceChannelMapper.stopBroadcast(deviceId);
+	}
+
 	/**
 	 * 获取设备
 	 *

+ 0 - 34
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/broadCast/BroadCast.java

@@ -1,34 +0,0 @@
-package com.genersoft.iot.vmp.vmanager.gb28181.broadCast;
-
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
-import com.genersoft.iot.vmp.vmanager.gb28181.play.PlayController;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.context.request.async.DeferredResult;
-
-@Tag(name  = "国标设备语音通话")
-@CrossOrigin
-@RestController
-@RequestMapping("/api/broader")
-public class BroadCast {
-    private final static Logger logger = LoggerFactory.getLogger(PlayController.class);
-    @Autowired
-    private IVideoManagerStorage storager;
-
-    @Operation(summary="语音对讲开始")
-    @Parameter(name = "deviceId", description = "设备id" , required = false)
-    @Parameter(name = "channelId", description = "通道id" , required = false)
-    @GetMapping("/start")
-    public WVPResult start(@RequestParam String deviceId,@RequestParam String channelId){
-        WVPResult result = new WVPResult<>();
-        // todo 完成 broaderCast 信息发送
-
-        return result;
-    }
-}

+ 48 - 59
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -84,7 +84,6 @@ public class PlayController {
 		Device device = storager.queryVideoDevice(deviceId);
 		MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
 		PlayResult playResult = playService.play(newMediaServerItem, deviceId, channelId, null, null, null);
-
 		return playResult.getResult();
 	}
 
@@ -204,69 +203,59 @@ public class PlayController {
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
     @GetMapping("/broadcast")
 //    @PostMapping("/broadcast/{deviceId}")
-    public DeferredResult<String> broadcastApi(@RequestParam String deviceId) {
+    public DeferredResult<WVPResult<String>> broadcastApi(@RequestParam String deviceId) {
         if (logger.isDebugEnabled()) {
             logger.debug("语音广播API调用");
         }
+// 获取可用的zlm
+//		开启推流接口
+		Device device = storager.queryVideoDevice(deviceId);
+		MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
 
-        Device device = storager.queryVideoDevice(deviceId);
-		DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
-		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
-		if (resultHolder.exist(key, null)) {
-			result.setResult("设备使用中");
-			return result;
-		}
-		String uuid  = UUID.randomUUID().toString();
-        if (device == null) {
-
-			resultHolder.put(key, key,  result);
-			RequestMessage msg = new RequestMessage();
-			msg.setKey(key);
-			msg.setId(uuid);
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("CmdType", "Broadcast");
-			json.put("Result", "Failed");
-			json.put("Description", "Device 不存在");
-			msg.setData(json);
-			resultHolder.invokeResult(msg);
-			return result;
-		}
-		// 发送广播命令
-		try {
-			cmder.audioBroadcastCmd(device, (event) -> {
-				RequestMessage msg = new RequestMessage();
-				msg.setKey(key);
-				msg.setId(uuid);
-				JSONObject json = new JSONObject();
-				json.put("DeviceID", deviceId);
-				json.put("CmdType", "Broadcast");
-				json.put("Result", "Failed");
-				json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
-				msg.setData(json);
-				resultHolder.invokeResult(msg);
-			});
-			// 收到broadcast回复后,去向zlm申请推流端口
-		} catch (InvalidArgumentException | SipException | ParseException e) {
-			logger.error("[命令发送失败] 语音广播: {}", e.getMessage());
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
-		}
+		PlayResult playResult = playService.openBroadcast(newMediaServerItem, deviceId, null, null, null);
+		return playResult.getResult();
 
-		result.onTimeout(() -> {
-			logger.warn("语音广播操作超时, 设备未返回应答指令");
-			RequestMessage msg = new RequestMessage();
-			msg.setKey(key);
-			msg.setId(uuid);
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("CmdType", "Broadcast");
-			json.put("Result", "Failed");
-			json.put("Error", "Timeout. Device did not response to broadcast command.");
-			msg.setData(json);
-			resultHolder.invokeResult(msg);
-		});
-		resultHolder.put(key, uuid, result);
-		return result;
+//        Device device = storager.queryVideoDevice(deviceId);
+//		DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
+//		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
+//		if (resultHolder.exist(key, null)) {
+//			result.setResult("设备使用中");
+//			return result;
+//		}
+//		String uuid  = UUID.randomUUID().toString();
+//        if (device == null) {
+//
+//			resultHolder.put(key, key,  result);
+//			RequestMessage msg = new RequestMessage();
+//			msg.setKey(key);
+//			msg.setId(uuid);
+//			JSONObject json = new JSONObject();
+//			json.put("DeviceID", deviceId);
+//			json.put("CmdType", "Broadcast");
+//			json.put("Result", "Failed");
+//			json.put("Description", "Device 不存在");
+//			msg.setData(json);
+//			resultHolder.invokeResult(msg);
+//			return result;
+//		}
+//		// 发送广播命令
+//
+//
+//		result.onTimeout(() -> {
+//			logger.warn("语音广播操作超时, 设备未返回应答指令");
+//			RequestMessage msg = new RequestMessage();
+//			msg.setKey(key);
+//			msg.setId(uuid);
+//			JSONObject json = new JSONObject();
+//			json.put("DeviceID", deviceId);
+//			json.put("CmdType", "Broadcast");
+//			json.put("Result", "Failed");
+//			json.put("Error", "Timeout. Device did not response to broadcast command.");
+//			msg.setData(json);
+//			resultHolder.invokeResult(msg);
+//		});
+//		resultHolder.put(key, uuid, result);
+//		return result;
 	}
 
 	@Operation(summary = "获取所有的ssrc")

+ 3 - 3
src/main/resources/application.yml

@@ -123,13 +123,13 @@ media:
     # [必须修改] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId
     id: your_server_id
     # [必须修改] zlm服务器的内网IP
-    ip: 192.168.1.211
+    ip: 192.168.1.203
     # [可选] 返回流地址时的ip,置空使用 media.ip
-    stream-ip: 192.168.1.211
+    stream-ip: 192.168.1.203
     #stream-ip: 192.168.1.203
     #stream-ip: 113.88.194.58
     # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip
-    sdp-ip: 192.168.1.211
+    sdp-ip: 192.168.1.203
     #sdp-ip: 192.168.1.203
     #sdp-ip: 113.88.194.58
     # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip

+ 7 - 5
web_src/config/index.js

@@ -12,25 +12,27 @@ module.exports = {
     assetsPublicPath: '/',
     proxyTable: {
       '/debug': {
-        target: 'https://localhost:29001',
+        target: 'https://192.168.1.26:29001',
         changeOrigin: true,
         secure: false,
         pathRewrite: {
-          '^/debug': '/'
+          '^/debug': ''
+        },headers: {
+          Referer: 'https://192.168.1.26:29001'
         }
       },
       '/aiLib':{
-        target: 'https://localhost:29001',
+        target: 'https://192.168.1.26:29001',
         secure: false,
         changeOrigin: true,
       },
       '/mFile':{
-        target: 'http://localhost:29001',
+        target: 'https://192.168.1.26:29001',
         secure: false,
         changeOrigin: true,
       },
       '/static/snap': {
-        target: 'https://localhost:29001',
+        target: 'https://192.168.1.26:29001',
         secure: false,
         changeOrigin: true,
         // pathRewrite: {

+ 2 - 2
web_src/src/components/channelList.vue

@@ -33,8 +33,8 @@
     </div>
   </div>
     <ptz-control ref="ptzControl"/>
-<!--  <devicePlayer ref="devicePlayer" ></devicePlayer>-->
-    <custom-player ref="devicePlayer" @close="closeHandle()"></custom-player>
+  <devicePlayer ref="devicePlayer" ></devicePlayer>
+<!--    <custom-player ref="devicePlayer" @close="closeHandle()"></custom-player>-->
   <el-container v-loading="isLoging" style="height: 82vh;">
     <el-aside width="auto" style="height: 82vh; background-color: #ffffff; overflow: auto" v-if="showTree" >
       <DeviceTree ref="deviceTree" :device="device" :onlyCatalog="true" :clickEvent="treeNodeClickEvent" ></DeviceTree>

+ 10 - 1
web_src/src/components/common/microphone.vue

@@ -81,7 +81,16 @@ export default {
         url: url
       }));
       this.clickCount = 0;
-      if(err){console.error(err)}
+      if(err){
+        console.error(err)
+        console.warn(err.message)
+      }
+      let response = res.data;
+
+      console.log(res);
+      if(response.code !== 0){
+        return this.$message.error(response.msg)
+      }
 
     }
   }