| | |
| | |
|
| | | public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
|
| | |
|
| | | public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP";
|
| | |
|
| | | private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
|
| | |
|
| | |
|
| | |
| | | import com.genersoft.iot.vmp.service.IPlayService; |
| | | import com.genersoft.iot.vmp.service.IStreamProxyService; |
| | | import com.genersoft.iot.vmp.service.IStreamPushService; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCode; |
| | | import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; |
| | | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| | |
| | | |
| | | Long finalStartTime = startTime; |
| | | Long finalStopTime = stopTime; |
| | | InviteErrorCallback<Object> hookEvent = (code, msg, data) -> { |
| | | ErrorCallback<Object> hookEvent = (code, msg, data) -> { |
| | | StreamInfo streamInfo = (StreamInfo)data; |
| | | MediaServerItem mediaServerItemInUSe = mediaServerService.getOne(streamInfo.getMediaServerId()); |
| | | logger.info("[上级Invite]下级已经开始推流。 回复200OK(SDP), {}/{}", streamInfo.getApp(), streamInfo.getStream()); |
| | |
| | | logger.error("[命令发送失败] 国标级联 回复SdpAck", e); |
| | | } |
| | | }; |
| | | InviteErrorCallback<Object> errorEvent = ((statusCode, msg, data) -> { |
| | | ErrorCallback<Object> errorEvent = ((statusCode, msg, data) -> { |
| | | // 未知错误。直接转发设备点播的错误 |
| | | try { |
| | | if (statusCode > 0) { |
| | |
| | | |
| | | public void sendGetForImg(MediaServerItem mediaServerItem, String api, Map<String, Object> params, String targetPath, String fileName) { |
| | | String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); |
| | | logger.debug(url); |
| | | HttpUrl parseUrl = HttpUrl.parse(url); |
| | | if (parseUrl == null) { |
| | | return; |
| | |
| | | |
| | | import com.genersoft.iot.vmp.common.InviteInfo; |
| | | import com.genersoft.iot.vmp.common.InviteSessionType; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| | | |
| | | /** |
| | | * 记录国标点播的状态,包括实时预览,下载,录像回放 |
| | |
| | | /** |
| | | * 添加一个invite回调 |
| | | */ |
| | | void once(InviteSessionType type, String deviceId, String channelId, String stream, InviteErrorCallback<Object> callback); |
| | | void once(InviteSessionType type, String deviceId, String channelId, String stream, ErrorCallback<Object> callback); |
| | | |
| | | /** |
| | | * 调用一个invite回调 |
| | |
| | | import com.genersoft.iot.vmp.conf.exception.ServiceException; |
| | | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| | | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| | | |
| | | import javax.sip.InvalidArgumentException; |
| | |
| | | public interface IPlayService { |
| | | |
| | | void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, |
| | | InviteErrorCallback<Object> callback); |
| | | SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, InviteErrorCallback<Object> callback); |
| | | ErrorCallback<Object> callback); |
| | | SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback); |
| | | |
| | | MediaServerItem getNewMediaServerItem(Device device); |
| | | |
| | |
| | | */ |
| | | MediaServerItem getNewMediaServerItemHasAssist(Device device); |
| | | |
| | | void playBack(String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback<Object> callback); |
| | | void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback<Object> callback); |
| | | void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback); |
| | | void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback); |
| | | |
| | | void zlmServerOffline(String mediaServerId); |
| | | |
| | | void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback); |
| | | void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback); |
| | | void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback); |
| | | void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback); |
| | | |
| | | StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream); |
| | | |
| | |
| | | void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| | | |
| | | void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| | | |
| | | void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); |
| | | } |
File was renamed from src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java |
| | |
| | | package com.genersoft.iot.vmp.service.bean; |
| | | |
| | | public interface InviteErrorCallback<T> { |
| | | public interface ErrorCallback<T> { |
| | | |
| | | void run(int code, String msg, T data); |
| | | } |
| | |
| | | */ |
| | | public enum InviteErrorCode { |
| | | SUCCESS(0, "成功"), |
| | | FAIL(-100, "失败"), |
| | | ERROR_FOR_SIGNALLING_TIMEOUT(-1, "信令超时"), |
| | | ERROR_FOR_STREAM_TIMEOUT(-2, "收流超时"), |
| | | ERROR_FOR_RESOURCE_EXHAUSTION(-3, "资源耗尽"), |
| | |
| | | import com.genersoft.iot.vmp.common.InviteSessionType; |
| | | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| | | import com.genersoft.iot.vmp.service.IInviteStreamService; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| | | import com.genersoft.iot.vmp.utils.redis.RedisUtil; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | |
| | | |
| | | private final Logger logger = LoggerFactory.getLogger(InviteStreamServiceImpl.class); |
| | | |
| | | private final Map<String, List<InviteErrorCallback<Object>>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); |
| | | private final Map<String, List<ErrorCallback<Object>>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); |
| | | |
| | | @Autowired |
| | | private RedisTemplate<Object, Object> redisTemplate; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void once(InviteSessionType type, String deviceId, String channelId, String stream, InviteErrorCallback<Object> callback) { |
| | | public void once(InviteSessionType type, String deviceId, String channelId, String stream, ErrorCallback<Object> callback) { |
| | | String key = buildKey(type, deviceId, channelId, stream); |
| | | List<InviteErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); |
| | | List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); |
| | | if (callbacks == null) { |
| | | callbacks = new CopyOnWriteArrayList<>(); |
| | | inviteErrorCallbackMap.put(key, callbacks); |
| | |
| | | @Override |
| | | public void call(InviteSessionType type, String deviceId, String channelId, String stream, int code, String msg, Object data) { |
| | | String key = buildKey(type, deviceId, channelId, stream); |
| | | List<InviteErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); |
| | | List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); |
| | | if (callbacks == null) { |
| | | return; |
| | | } |
| | | for (InviteErrorCallback<Object> callback : callbacks) { |
| | | for (ErrorCallback<Object> callback : callbacks) { |
| | | callback.run(code, msg, data); |
| | | } |
| | | inviteErrorCallbackMap.remove(key); |
| | |
| | | import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; |
| | | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| | | import com.genersoft.iot.vmp.service.*; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCode; |
| | | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | |
| | | import javax.sip.InvalidArgumentException; |
| | | import javax.sip.ResponseEvent; |
| | | import javax.sip.SipException; |
| | | import java.io.File; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.text.ParseException; |
| | |
| | | |
| | | |
| | | @Override |
| | | public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, InviteErrorCallback<Object> callback) { |
| | | public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback) { |
| | | if (mediaServerItem == null) { |
| | | throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); |
| | | } |
| | |
| | | |
| | | @Override |
| | | public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, |
| | | InviteErrorCallback<Object> callback) { |
| | | ErrorCallback<Object> callback) { |
| | | |
| | | if (mediaServerItem == null || ssrcInfo == null) { |
| | | callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), |
| | |
| | | |
| | | @Override |
| | | public void playBack(String deviceId, String channelId, String startTime, |
| | | String endTime, InviteErrorCallback<Object> callback) { |
| | | String endTime, ErrorCallback<Object> callback) { |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | if (device == null) { |
| | | return; |
| | |
| | | @Override |
| | | public void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, |
| | | String deviceId, String channelId, String startTime, |
| | | String endTime, InviteErrorCallback<Object> callback) { |
| | | String endTime, ErrorCallback<Object> callback) { |
| | | if (mediaServerItem == null || ssrcInfo == null) { |
| | | callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), |
| | | InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), |
| | |
| | | |
| | | |
| | | @Override |
| | | public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback) { |
| | | public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) { |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | if (device == null) { |
| | | return; |
| | |
| | | |
| | | |
| | | @Override |
| | | public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback) { |
| | | public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) { |
| | | if (mediaServerItem == null || ssrcInfo == null) { |
| | | callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), |
| | | InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), |
| | |
| | | Device device = storager.queryVideoDevice(inviteInfo.getDeviceId()); |
| | | cmder.playResumeCmd(device, inviteInfo.getStreamInfo()); |
| | | } |
| | | |
| | | @Override |
| | | public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) { |
| | | Device device = deviceService.getDevice(deviceId); |
| | | if (device == null) { |
| | | errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); |
| | | return; |
| | | } |
| | | |
| | | InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); |
| | | if (inviteInfo != null) { |
| | | if (inviteInfo.getStreamInfo() != null) { |
| | | // 已存在线直接截图 |
| | | MediaServerItem mediaServerItemInuse = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId()); |
| | | String streamUrl; |
| | | if (mediaServerItemInuse.getRtspPort() != 0) { |
| | | streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", inviteInfo.getStreamInfo().getStream()); |
| | | }else { |
| | | streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServerItemInuse.getHttpPort(), "rtp", inviteInfo.getStreamInfo().getStream()); |
| | | } |
| | | String path = "snap"; |
| | | // 请求截图 |
| | | logger.info("[请求截图]: " + fileName); |
| | | zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName); |
| | | File snapFile = new File(path + File.separator + fileName); |
| | | if (snapFile.exists()) { |
| | | errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapFile.getAbsoluteFile()); |
| | | }else { |
| | | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); |
| | | } |
| | | return; |
| | | } |
| | | } |
| | | |
| | | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); |
| | | play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{ |
| | | if (code == InviteErrorCode.SUCCESS.getCode()) { |
| | | InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); |
| | | if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { |
| | | getSnap(deviceId, channelId, fileName, errorCallback); |
| | | }else { |
| | | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); |
| | | } |
| | | }else { |
| | | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | public static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; |
| | | |
| | | /** |
| | | * wvp内部统一时间格式 |
| | | */ |
| | | public static final String URL_PATTERN = "yyyyMMddHHmmss"; |
| | | |
| | | public static final String zoneStr = "Asia/Shanghai"; |
| | | |
| | | public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); |
| | | public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); |
| | | public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); |
| | | public static final DateTimeFormatter urlFormatter = DateTimeFormatter.ofPattern(URL_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); |
| | | |
| | | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 获取当前时间 |
| | | * @return |
| | | */ |
| | | public static String getNowForUrl() { |
| | | LocalDateTime nowDateTime = LocalDateTime.now(); |
| | | return urlFormatter.format(nowDateTime); |
| | | } |
| | | |
| | | /** |
| | | * 格式校验 |
| | | * @param timeStr 时间字符串 |
| | | * @param dateTimeFormatter 待校验的格式 |
| | |
| | | import com.genersoft.iot.vmp.service.bean.InviteErrorCode; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| | | import com.genersoft.iot.vmp.utils.DateUtil; |
| | | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; |
| | | import com.genersoft.iot.vmp.vmanager.bean.StreamContent; |
| | | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| | |
| | | return jsonObject; |
| | | } |
| | | |
| | | @Operation(summary = "获取截图") |
| | | @Parameter(name = "deviceId", description = "设备国标编号", required = true) |
| | | @Parameter(name = "channelId", description = "通道国标编号", required = true) |
| | | @GetMapping("/snap") |
| | | public DeferredResult<String> getSnap(String deviceId, String channelId) { |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug("获取截图: {}/{}", deviceId, channelId); |
| | | } |
| | | |
| | | DeferredResult<String> result = new DeferredResult<>(3 * 1000L); |
| | | String key = DeferredResultHolder.CALLBACK_CMD_SNAP + deviceId; |
| | | String uuid = UUID.randomUUID().toString(); |
| | | resultHolder.put(key, uuid, result); |
| | | |
| | | RequestMessage message = new RequestMessage(); |
| | | message.setKey(key); |
| | | message.setId(uuid); |
| | | |
| | | String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + "jpg"; |
| | | playService.getSnap(deviceId, channelId, fileName, (code, msg, data) -> { |
| | | if (code == InviteErrorCode.SUCCESS.getCode()) { |
| | | message.setData(data); |
| | | }else { |
| | | message.setData(WVPResult.fail(code, msg)); |
| | | } |
| | | resultHolder.invokeResult(message); |
| | | }); |
| | | return result; |
| | | } |
| | | |
| | | } |
| | | |