Kaynağa Gözat

优化按需拉流配置,拉流代理支持按需拉流

648540858 3 yıl önce
ebeveyn
işleme
688e222dcc

+ 0 - 1
sql/mysql.sql

@@ -277,7 +277,6 @@ CREATE TABLE `media_server` (
                                 `rtspSSLPort` int NOT NULL,
                                 `autoConfig` int NOT NULL,
                                 `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                                `streamNoneReaderDelayMS` int NOT NULL,
                                 `rtpEnable` int NOT NULL,
                                 `rtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                 `sendRtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 5 - 0
sql/update.sql

@@ -0,0 +1,5 @@
+alter table wvp.media_server
+    drop column streamNoneReaderDelayMS;
+
+alter table stream_proxy
+    add enable_disable_none_reader bit(1) default null;

+ 0 - 8
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -69,9 +69,6 @@ public class MediaConfig{
     @Value("${media.secret}")
     private String secret;
 
-    @Value("${media.stream-none-reader-delay-ms:15000}")
-    private int streamNoneReaderDelayMS = 15000;
-
     @Value("${media.rtp.enable}")
     private boolean rtpEnable;
 
@@ -151,10 +148,6 @@ public class MediaConfig{
         return secret;
     }
 
-    public int getStreamNoneReaderDelayMS() {
-        return streamNoneReaderDelayMS;
-    }
-
     public boolean isRtpEnable() {
         return rtpEnable;
     }
@@ -219,7 +212,6 @@ public class MediaConfig{
         mediaServerItem.setRtspSSLPort(rtspSSLPort);
         mediaServerItem.setAutoConfig(autoConfig);
         mediaServerItem.setSecret(secret);
-        mediaServerItem.setStreamNoneReaderDelayMS(streamNoneReaderDelayMS);
         mediaServerItem.setRtpEnable(rtpEnable);
         mediaServerItem.setRtpPortRange(rtpPortRange);
         mediaServerItem.setSendRtpPortRange(sendRtpPortRange);

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java

@@ -33,6 +33,8 @@ public class UserSetting {
 
     private Boolean usePushingAsStatus = Boolean.TRUE;
 
+    private Boolean streamOnDemand = Boolean.TRUE;
+
     private String serverId = "000000";
 
     private String thirdPartyGBIdReg = "[\\s\\S]*";
@@ -146,4 +148,12 @@ public class UserSetting {
     public void setUsePushingAsStatus(Boolean usePushingAsStatus) {
         this.usePushingAsStatus = usePushingAsStatus;
     }
+
+    public Boolean getStreamOnDemand() {
+        return streamOnDemand;
+    }
+
+    public void setStreamOnDemand(Boolean streamOnDemand) {
+        this.streamOnDemand = streamOnDemand;
+    }
 }

+ 56 - 37
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -558,9 +558,12 @@ public class ZLMHttpHookListener {
 		String app = json.getString("app");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
+		// 录像下载
+		ret.put("close", userSetting.getStreamOnDemand());
 		if ("rtp".equals(app)){
-			ret.put("close", true);
+			// 国标流, 点播/录像回放/录像下载
 			StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
+			// 点播
 			if (streamInfoForPlayCatch != null) {
 				// 收到无人观看说明流也没有在往上级推送
 				if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
@@ -590,40 +593,39 @@ public class ZLMHttpHookListener {
 
 				redisCatchStorage.stopPlay(streamInfoForPlayCatch);
 				storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
-			}else{
-				StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, streamId, null);
-				if (streamInfoForPlayBackCatch != null ) {
-					if (streamInfoForPlayBackCatch.isPause()) {
-						ret.put("close", false);
-					}else {
-						Device device = deviceService.queryDevice(streamInfoForPlayBackCatch.getDeviceID());
-						if (device != null) {
-							try {
-								cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(),
-										streamInfoForPlayBackCatch.getStream(), null);
-							} catch (InvalidArgumentException | ParseException | SipException |
-									 SsrcTransactionNotFoundException e) {
-								logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
-							}
-						}
-						redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
-								streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
-					}
-
+				return ret;
+			}
+			// 录像回放
+			StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, streamId, null);
+			if (streamInfoForPlayBackCatch != null ) {
+				if (streamInfoForPlayBackCatch.isPause()) {
+					ret.put("close", false);
 				}else {
-					StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, streamId, null);
-					// 进行录像下载时无人观看不断流
-					if (streamInfoForDownload != null) {
-						ret.put("close", false);
+					Device device = deviceService.queryDevice(streamInfoForPlayBackCatch.getDeviceID());
+					if (device != null) {
+						try {
+							cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(),
+									streamInfoForPlayBackCatch.getStream(), null);
+						} catch (InvalidArgumentException | ParseException | SipException |
+								 SsrcTransactionNotFoundException e) {
+							logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
+						}
 					}
+					redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
+							streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
 				}
+				return ret;
 			}
-			MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
-			if (mediaServerItem != null && mediaServerItem.getStreamNoneReaderDelayMS() == -1) {
+			// 录像下载
+			StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, streamId, null);
+			// 进行录像下载时无人观看不断流
+			if (streamInfoForDownload != null) {
 				ret.put("close", false);
+				return ret;
 			}
-			return ret;
 		}else {
+			// 非国标流 推流/拉流代理
+			// 拉流代理
 			StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
 			if (streamProxyItem != null ) {
 				if (streamProxyItem.isEnable_remove_none_reader()) {
@@ -635,12 +637,21 @@ public class ZLMHttpHookListener {
 				}else if (streamProxyItem.isEnable_disable_none_reader()) {
 					// 无人观看停用
 					ret.put("close", true);
+					// 修改数据
+					streamProxyService.stop(app, streamId);
 				}else {
 					ret.put("close", false);
 				}
+				return ret;
 			}
-			return ret;
+			// 推流具有主动性,暂时不做处理
+//			StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
+//			if (streamPushItem != null) {
+//				// TODO 发送停止
+//
+//			}
 		}
+		return ret;
 	}
 	
 	/**
@@ -655,19 +666,27 @@ public class ZLMHttpHookListener {
 		}
 		String mediaServerId = json.getString("mediaServerId");
 		MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-		if (userSetting.isAutoApplyPlay() && mediaInfo != null && mediaInfo.isRtpEnable()) {
+		if (userSetting.isAutoApplyPlay() && mediaInfo != null) {
 			String app = json.getString("app");
 			String streamId = json.getString("stream");
 			if ("rtp".equals(app)) {
-				String[] s = streamId.split("_");
-				if (s.length == 2) {
-					String deviceId = s[0];
-					String channelId = s[1];
-					Device device = redisCatchStorage.getDevice(deviceId);
-					if (device != null) {
-						playService.play(mediaInfo,deviceId, channelId, null, null, null);
+				if (mediaInfo.isRtpEnable()) {
+					String[] s = streamId.split("_");
+					if (s.length == 2) {
+						String deviceId = s[0];
+						String channelId = s[1];
+						Device device = redisCatchStorage.getDevice(deviceId);
+						if (device != null) {
+							playService.play(mediaInfo,deviceId, channelId, null, null, null);
+						}
 					}
 				}
+			}else {
+				// 拉流代理
+				StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
+				if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnable_disable_none_reader()) {
+					streamProxyService.start(app, streamId);
+				}
 			}
 		}
 

+ 0 - 12
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java

@@ -54,9 +54,6 @@ public class MediaServerItem{
     @Schema(description = "ZLM鉴权参数")
     private String secret;
 
-    @Schema(description = "某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒")
-    private int streamNoneReaderDelayMS;
-
     @Schema(description = "keepalive hook触发间隔,单位秒")
     private int hookAliveInterval;
 
@@ -119,7 +116,6 @@ public class MediaServerItem{
         rtspSSLPort = zlmServerConfig.getRtspSSlport();
         autoConfig = true; // 默认值true;
         secret = zlmServerConfig.getApiSecret();
-        streamNoneReaderDelayMS = zlmServerConfig.getGeneralStreamNoneReaderDelayMS();
         hookAliveInterval = zlmServerConfig.getHookAliveInterval();
         rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
         rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号
@@ -240,14 +236,6 @@ public class MediaServerItem{
         this.secret = secret;
     }
 
-    public int getStreamNoneReaderDelayMS() {
-        return streamNoneReaderDelayMS;
-    }
-
-    public void setStreamNoneReaderDelayMS(int streamNoneReaderDelayMS) {
-        this.streamNoneReaderDelayMS = streamNoneReaderDelayMS;
-    }
-
     public boolean isRtpEnable() {
         return rtpEnable;
     }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java

@@ -38,7 +38,7 @@ public class StreamProxyItem extends GbStream {
     @Schema(description = "是否 无人观看时删除")
     private boolean enable_remove_none_reader;
 
-    @Schema(description = "是否 无人观看时不启用")
+    @Schema(description = "是否 无人观看时自动停用")
     private boolean enable_disable_none_reader;
     @Schema(description = "上级平台国标ID")
     private String platformGbId;

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

@@ -541,7 +541,6 @@ public class MediaServerServiceImpl implements IMediaServerService {
             param.put("hook.on_record_mp4","");
         }
         param.put("hook.timeoutSec","20");
-        param.put("general.streamNoneReaderDelayMS",mediaServerItem.getStreamNoneReaderDelayMS()==-1?"3600000":mediaServerItem.getStreamNoneReaderDelayMS() );
         // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
         // 置0关闭此特性(推流断开会导致立即断开播放器)
         // 此参数不应大于播放器超时时间
@@ -606,7 +605,6 @@ public class MediaServerServiceImpl implements IMediaServerService {
         mediaServerItem.setStreamIp(ip);
         mediaServerItem.setHookIp(sipConfig.getIp());
         mediaServerItem.setSdpIp(ip);
-        mediaServerItem.setStreamNoneReaderDelayMS(zlmServerConfig.getGeneralStreamNoneReaderDelayMS());
         return mediaServerItem;
     }
 

+ 0 - 4
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java

@@ -26,7 +26,6 @@ public interface MediaServerMapper {
             "rtspSSLPort, " +
             "autoConfig, " +
             "secret, " +
-            "streamNoneReaderDelayMS, " +
             "rtpEnable, " +
             "rtpPortRange, " +
             "sendRtpPortRange, " +
@@ -51,7 +50,6 @@ public interface MediaServerMapper {
             "${rtspSSLPort}, " +
             "${autoConfig}, " +
             "'${secret}', " +
-            "${streamNoneReaderDelayMS}, " +
             "${rtpEnable}, " +
             "'${rtpPortRange}', " +
             "'${sendRtpPortRange}', " +
@@ -77,7 +75,6 @@ public interface MediaServerMapper {
             "<if test=\"rtspPort != null\">, rtspPort=${rtspPort}</if>" +
             "<if test=\"rtspSSLPort != null\">, rtspSSLPort=${rtspSSLPort}</if>" +
             "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
-            "<if test=\"streamNoneReaderDelayMS != null\">, streamNoneReaderDelayMS=${streamNoneReaderDelayMS}</if>" +
             "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
             "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
             "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +
@@ -102,7 +99,6 @@ public interface MediaServerMapper {
             "<if test=\"rtspPort != null\">, rtspPort=${rtspPort}</if>" +
             "<if test=\"rtspSSLPort != null\">, rtspSSLPort=${rtspSSLPort}</if>" +
             "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
-            "<if test=\"streamNoneReaderDelayMS != null\">, streamNoneReaderDelayMS=${streamNoneReaderDelayMS}</if>" +
             "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
             "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
             "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +

+ 3 - 2
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java

@@ -11,10 +11,10 @@ import java.util.List;
 public interface StreamProxyMapper {
 
     @Insert("INSERT INTO stream_proxy (type, name, app, stream,mediaServerId, url, src_url, dst_url, " +
-            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, status, enable_remove_none_reader, createTime) VALUES" +
+            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, status, enable_remove_none_reader, enable_disable_none_reader, createTime) VALUES" +
             "('${type}','${name}', '${app}', '${stream}', '${mediaServerId}','${url}', '${src_url}', '${dst_url}', " +
             "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable}, ${status}, " +
-            "${enable_remove_none_reader}, '${createTime}' )")
+            "${enable_remove_none_reader}, ${enable_disable_none_reader}, '${createTime}' )")
     int add(StreamProxyItem streamProxyDto);
 
     @Update("UPDATE stream_proxy " +
@@ -33,6 +33,7 @@ public interface StreamProxyMapper {
             "enable=#{enable}, " +
             "status=#{status}, " +
             "enable_remove_none_reader=#{enable_remove_none_reader}, " +
+            "enable_disable_none_reader=#{enable_disable_none_reader}, " +
             "enable_mp4=#{enable_mp4} " +
             "WHERE app=#{app} AND stream=#{stream}")
     int update(StreamProxyItem streamProxyDto);

+ 2 - 2
src/main/resources/all-application.yml

@@ -146,8 +146,6 @@ media:
     auto-config: true
     # [可选] zlm服务器的hook.admin_params=secret
     secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
-    # [可选] zlm服务器的general.streamNoneReaderDelayMS
-    stream-none-reader-delay-ms:  18000  # 无人观看多久自动关闭流, -1表示永不自动关闭,即 关闭按需拉流
     # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
     rtp:
         # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
@@ -190,6 +188,8 @@ user-settings:
     logInDatebase: true
     # 使用推流状态作为推流通道状态
     use-pushing-as-status: true
+    # 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放
+    stream-on-demand: true
 
 # 关闭在线文档(生产环境建议关闭)
 springdoc:

+ 4 - 9
web_src/src/components/dialog/MediaServerEdit.vue

@@ -41,10 +41,6 @@
                 <el-input  v-if="currentStep === 2"  v-model="mediaServerForm.httpPort" disabled :disabled="mediaServerForm.defaultServer"></el-input>
                 <el-input  v-if="currentStep === 3"  v-model="mediaServerForm.httpPort" :disabled="mediaServerForm.defaultServer"></el-input>
               </el-form-item>
-              <el-form-item label="SECRET" prop="secret">
-                <el-input v-if="currentStep === 2"  v-model="mediaServerForm.secret" disabled :disabled="mediaServerForm.defaultServer"></el-input>
-                <el-input v-if="currentStep === 3"  v-model="mediaServerForm.secret" :disabled="mediaServerForm.defaultServer"></el-input>
-              </el-form-item>
               <el-form-item label="HOOK IP" prop="ip">
                 <el-input v-model="mediaServerForm.hookIp" placeholder="媒体服务HOOK_IP" clearable :disabled="mediaServerForm.defaultServer"></el-input>
               </el-form-item>
@@ -74,6 +70,10 @@
               <el-form-item label="RTMPS PORT" prop="rtmpSSlPort">
                 <el-input v-model="mediaServerForm.rtmpSSlPort" placeholder="媒体服务RTMPS_PORT" clearable :disabled="mediaServerForm.defaultServer"></el-input>
               </el-form-item>
+              <el-form-item label="SECRET" prop="secret">
+                <el-input v-if="currentStep === 2"  v-model="mediaServerForm.secret" disabled :disabled="mediaServerForm.defaultServer"></el-input>
+                <el-input v-if="currentStep === 3"  v-model="mediaServerForm.secret" :disabled="mediaServerForm.defaultServer"></el-input>
+              </el-form-item>
               <el-form-item label="自动配置媒体服务" >
                 <el-switch v-model="mediaServerForm.autoConfig" :disabled="mediaServerForm.defaultServer"></el-switch>
               </el-form-item>
@@ -94,9 +94,6 @@
                 -
                 <el-input v-model="sendRtpPortRange2" placeholder="终止" @change="portRangeChange" clearable style="width: 100px" prop="sendRtpPortRange2" :disabled="mediaServerForm.defaultServer"></el-input>
               </el-form-item>
-              <el-form-item label="无人观看多久后停止拉流" >
-                <el-input v-model.number="mediaServerForm.streamNoneReaderDelayMS" clearable :disabled="mediaServerForm.defaultServer"></el-input>
-              </el-form-item>
               <el-form-item label="录像管理服务端口" prop="recordAssistPort">
                 <el-input v-model.number="mediaServerForm.recordAssistPort" :disabled="mediaServerForm.defaultServer">
 <!--                  <el-button v-if="mediaServerForm.recordAssistPort > 0" slot="append" type="primary" @click="checkRecordServer">测试</el-button>-->
@@ -172,7 +169,6 @@ export default {
         hookIp: "",
         sdpIp: "",
         streamIp: "",
-        streamNoneReaderDelayMS: "",
         secret: "035c73f7-bb6b-4889-a715-d9eb2d1925cc",
         httpPort: "",
         httpSSlPort: "",
@@ -332,7 +328,6 @@ export default {
         hookIp: "",
         sdpIp: "",
         streamIp: "",
-        streamNoneReaderDelayMS: "",
         secret: "035c73f7-bb6b-4889-a715-d9eb2d1925cc",
         httpPort: "",
         httpSSlPort: "",

+ 10 - 1
web_src/src/components/dialog/StreamProxyEdit.vue

@@ -105,7 +105,9 @@
                   <el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox>
                   <el-checkbox label="转HLS" v-model="proxyParam.enable_hls" ></el-checkbox>
                   <el-checkbox label="MP4录制" v-model="proxyParam.enable_mp4" ></el-checkbox>
-                  <el-checkbox label="无人观看自动删除" v-model="proxyParam.enable_remove_none_reader" ></el-checkbox>
+                  <el-checkbox label="无人观看自动删除" v-model="proxyParam.enable_remove_none_reader" @change="removeNoneReader"></el-checkbox>
+                  <el-checkbox label="无人观看停止拉流" v-model="proxyParam.enable_disable_none_reader" @change="disableNoneReaderHandType"></el-checkbox>
+
                 </div>
 
               </el-form-item>
@@ -170,6 +172,7 @@ export default {
           enable_hls: true,
           enable_mp4: false,
           enable_remove_none_reader: false,
+          enable_disable_none_reader: true,
           platformGbId: null,
           mediaServerId: null,
       },
@@ -276,6 +279,12 @@ export default {
       if (this.platform.enable && this.platform.expires == "0") {
         this.platform.expires = "300";
       }
+    },
+    removeNoneReader: function(checked) {
+      this.proxyParam.enable_disable_none_reader = !checked;
+    },
+    disableNoneReaderHandType: function(checked) {
+      this.proxyParam.enable_remove_none_reader = !checked;
     }
   },
 };

+ 0 - 3
web_src/src/components/setting/Media.vue

@@ -42,9 +42,6 @@
         <el-form-item label="接口密钥" prop="secret">
           <el-input v-model="form.secret" clearable></el-input>
         </el-form-item>
-        <el-form-item label="无人观看触发时长">
-          <el-input v-model.number="form.streamNoneReaderDelayMS" clearable></el-input>
-        </el-form-item>
         <el-form-item label="自动配置">
           <el-switch v-model="form.autoConfig"></el-switch>
         </el-form-item>