支持ABL录像回放

This commit is contained in:
lin
2025-09-17 18:19:33 +08:00
parent 403e7648f9
commit fb9ff67701
20 changed files with 314 additions and 141 deletions

View File

@@ -15,11 +15,12 @@ import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.event.media.MediaRecordProcessEvent;
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.utils.DateUtil;
@@ -32,6 +33,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -192,8 +195,10 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
streamInfoResult.setMediaInfo(mediaInfo);
streamInfoResult.setOriginType(mediaInfo.getOriginType());
if (mediaInfo != null) {
streamInfoResult.setMediaInfo(mediaInfo);
streamInfoResult.setOriginType(mediaInfo.getOriginType());
}
return streamInfoResult;
}
@@ -255,18 +260,18 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
}
// 接受进度通知
@EventListener
public void onApplicationEvent(MediaRecordProcessEvent event) {
CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName());
if (cloudRecordItem == null) {
cloudRecordItem = CloudRecordItem.getInstance(event);
cloudRecordItem.setStartTime(event.getStartTime());
cloudRecordItem.setEndTime(event.getEndTime());
cloudRecordServiceMapper.add(cloudRecordItem);
}else {
cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis());
}
}
// @EventListener
// public void onApplicationEvent(MediaRecordProcessEvent event) {
// CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName());
// if (cloudRecordItem == null) {
// cloudRecordItem = CloudRecordItem.getInstance(event);
// cloudRecordItem.setStartTime(event.getStartTime());
// cloudRecordItem.setEndTime(event.getEndTime());
// cloudRecordServiceMapper.add(cloudRecordItem);
// }else {
// cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis());
// }
// }
@EventListener
public void onApplicationEvent(MediaRecordMp4Event event) {
InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream());
@@ -401,8 +406,24 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
}
@Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
logger.warn("[abl-loadMP4File] 未实现");
public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
// 解析为 LocalDate
LocalDate localDate = LocalDate.parse(date, DateUtil.DateFormatter);
LocalDateTime startOfDay = localDate.atStartOfDay();
LocalDateTime endOfDay = localDate.atTime(23, 59,59, 999);
String startTime = DateUtil.urlFormatter.format(startOfDay);
String endTime = DateUtil.urlFormatter.format(endOfDay);
ABLResult ablResult = ablresTfulUtils.queryRecordList(mediaServer, app, stream, startTime, endTime);
if (ablResult.getCode() != 0) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ablResult.getMemo());
}
String resultApp = ablResult.getApp();
String resultStream = ablResult.getStream();
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, resultApp, resultStream, null, null, true);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
}
@Override
@@ -414,4 +435,39 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
logger.warn("[abl-setRecordSpeed] 未实现");
}
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
// 将filePath作为独立参数传入避免%符号解析问题
String pathTemplate = "%s://%s:%s/%s/%s__ReplayFMP4RecordFile__%s?download_speed=6";
DownloadFileInfo info = new DownloadFileInfo();
info.setHttpPath(
String.format(
pathTemplate,
"http",
mediaServer.getStreamIp(),
mediaServer.getHttpPort(),
recordInfo.getApp(),
recordInfo.getStream(),
recordInfo.getFileName()
)
);
if (mediaServer.getHttpSSlPort() > 0) {
info.setHttpsPath(
String.format(
pathTemplate,
"https",
mediaServer.getStreamIp(),
mediaServer.getHttpSSlPort(),
recordInfo.getApp(),
recordInfo.getStream(),
recordInfo.getFileName()
)
);
}
return info;
}
}

View File

@@ -1,8 +1,10 @@
package com.genersoft.iot.vmp.media.abl;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -12,8 +14,10 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -426,6 +430,12 @@ public class ABLRESTfulUtils {
}
public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>();
param.put("app", app);
param.put("stream", stream);
@@ -443,6 +453,11 @@ public class ABLRESTfulUtils {
}
public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>();
param.put("app", app);
param.put("stream", stream);

View File

@@ -21,7 +21,7 @@ public class ABLResult {
private String starttime;
private String endtime;
private ABLUrls url;
private ABLRecordFile recordFileList;
private List<ABLRecordFile> recordFileList;
public static ABLResult getFailForMediaServer() {
ABLResult zlmResult = new ABLResult();

View File

@@ -1,13 +1,13 @@
package com.genersoft.iot.vmp.media.abl.bean.hook;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OnRecordMp4ABLHookParam extends ABLHookParam{
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
private String startTime;
private String endTime;
private long fileSize;
}

View File

@@ -41,6 +41,12 @@ public class MediaServer {
@Schema(description = "https-flv端口")
private int flvSSLPort;
@Schema(description = "mp4端口")
private int mp4Port;
@Schema(description = "https-mp4端口")
private int mp4SSLPort;
@Schema(description = "ws-flv端口")
private int wsFlvPort;

View File

@@ -2,34 +2,63 @@ package com.genersoft.iot.vmp.media.bean;
import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.utils.DateUtil;
import lombok.Data;
@Data
public class RecordInfo {
private String app;
private String stream;
private String fileName;
private String filePath;
private long fileSize;
private String folder;
private String url;
/**
* 单位毫秒
*/
private long startTime;
/**
* 单位毫秒
*/
private double timeLen;
private String params;
public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) {
RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(hookParam.getApp());
recordInfo.setStream(hookParam.getStream());
recordInfo.setFileName(hookParam.getFile_name());
recordInfo.setUrl(hookParam.getUrl());
recordInfo.setFolder(hookParam.getFolder());
recordInfo.setFilePath(hookParam.getFile_path());
recordInfo.setFileSize(hookParam.getFile_size());
recordInfo.setStartTime(hookParam.getStart_time());
recordInfo.setTimeLen(hookParam.getTime_len());
recordInfo.setStartTime(hookParam.getStart_time() * 1000);
recordInfo.setTimeLen(hookParam.getTime_len() * 1000);
return recordInfo;
}
public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) {
RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(hookParam.getApp());
recordInfo.setStream(hookParam.getStream());
recordInfo.setFileName(hookParam.getFileName());
recordInfo.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime()));
recordInfo.setTimeLen(DateUtil.urlToTimestampMs(hookParam.getEndTime()) - recordInfo.getStartTime());
recordInfo.setFileSize(hookParam.getFileSize());
return recordInfo;
}
public static RecordInfo getInstance(CloudRecordItem cloudRecordItem) {
RecordInfo recordInfo = new RecordInfo();
recordInfo.setApp(cloudRecordItem.getApp());
recordInfo.setStream(cloudRecordItem.getStream());
recordInfo.setFileName(cloudRecordItem.getFileName());
recordInfo.setStartTime(cloudRecordItem.getStartTime());
recordInfo.setTimeLen(cloudRecordItem.getTimeLen());
recordInfo.setFileSize(cloudRecordItem.getFileSize());
recordInfo.setFilePath(cloudRecordItem.getFilePath());
return recordInfo;
}

View File

@@ -5,6 +5,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -74,9 +77,11 @@ public interface IMediaNodeServerService {
List<String> listRtpServer(MediaServer mediaServer);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
}

View File

@@ -5,8 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -164,9 +164,11 @@ public interface IMediaServerService {
List<String> listRtpServer(MediaServer mediaServer);
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
}

View File

@@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent;
@@ -20,8 +21,7 @@ import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
@@ -1008,14 +1008,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
@Override
public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[loadMP4File] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath);
return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null);
mediaNodeServerService.loadMP4File(mediaServer, app, stream, date, dateDir, callback);
}
@Override
@@ -1037,4 +1037,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
}
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[setRecordSpeed] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
return mediaNodeServerService.getDownloadFilePath(mediaServer, recordInfo);
}
}

View File

@@ -10,8 +10,14 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -20,7 +26,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service("zlm")
@@ -36,6 +45,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
@Autowired
private UserSetting userSetting;
@Autowired
private HookSubscribe subscribe;
@Override
public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) {
return zlmServerFactory.createRTPServer(mediaServer, "rtp", streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode);
@@ -552,8 +564,28 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
@Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath);
public void loadMP4File(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
String buildApp = "mp4_record";
String buildStream = app + "_" + stream + "_" + date;
MediaInfo mediaInfo = getMediaInfo(mediaServer, buildApp, buildStream);
if (mediaInfo != null) {
if (callback != null) {
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null, true);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId());
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, true);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
});
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
if (zlmResult == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
@@ -583,4 +615,30 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg());
}
}
@Override
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, RecordInfo recordInfo) {
// 将filePath作为独立参数传入避免%符号解析问题
String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s";
DownloadFileInfo info = new DownloadFileInfo();
// filePath作为第4个参数
info.setHttpPath(String.format(pathTemplate,
"http",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpPort(),
recordInfo.getFilePath()));
// 同样作为第4个参数
if (mediaServerItem.getHttpSSlPort() > 0) {
info.setHttpsPath(String.format(pathTemplate,
"https",
mediaServerItem.getStreamIp(),
mediaServerItem.getHttpSSlPort(),
recordInfo.getFilePath()));
}
return info;
}
}

View File

@@ -4,8 +4,10 @@ import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;
@@ -15,9 +17,11 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -327,6 +331,12 @@ public class ZLMRESTfulUtils {
public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec,
boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
try {
src_url = URLEncoder.encode(src_url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>();
param.put("src_url", src_url);
param.put("dst_url", dst_url);
@@ -573,6 +583,11 @@ public class ZLMRESTfulUtils {
}
public ZLMResult<StreamProxyResult> addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) {
try {
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
}
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", app);