package com.ard.zlm.controller; import com.alibaba.fastjson2.JSONObject; import com.ard.common.core.constant.Constants; import com.ard.common.core.constant.SecurityConstants; import com.ard.common.core.domain.R; import com.ard.common.core.enums.LiveStreamType; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.web.domain.AjaxResult; import com.ard.gb28181.api.RemoteGb28181Service; import com.ard.gb28181.api.domain.Device; import com.ard.gb28181.api.domain.DeviceChannel; import com.ard.gb28181.api.domain.GbChannelDTO; import com.ard.zlm.domain.StreamChannel; import com.ard.zlm.service.ZlmStreamService; import com.ard.work.api.RemoteCameraService; import com.ard.zlm.api.domain.*; import com.ard.zlm.common.InviteErrorCode; import com.ard.zlm.common.InviteSessionType; import com.ard.zlm.config.UserSetting; import com.ard.zlm.domain.Snap; import com.ard.zlm.mediaServer.MediaServerChangeEvent; import com.ard.zlm.service.ErrorCallback; import com.ard.zlm.service.IInviteStreamService; import com.ard.zlm.service.IMediaServerService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import java.net.MalformedURLException; import java.net.URL; import java.util.List; /** * zlm 接口 * * @FileName ZlmController * @Description * @Author fengcheng * @date 2026-04-01 **/ @Tag(name = "zlm接口") @Slf4j @RestController public class ZlmController { @Resource private IMediaServerService mediaServerService; @Resource private UserSetting userSetting; @Resource private ApplicationEventPublisher applicationEventPublisher; @Resource private RemoteGb28181Service remoteGb28181Service; @Resource private ZlmStreamService zlmStreamService; @Resource @Lazy private IInviteStreamService inviteStreamService; /** * 拉流播放 * * @param streamPullPlay 拉流播放请求参数 * @param request HttpServletRequest * @return */ @Operation(summary = "拉流播放代理") @PostMapping("/streamPullPlay") public DeferredResult> streamPullPlay(@RequestBody StreamPullPlay streamPullPlay, HttpServletRequest request) { log.info("拉流播放代理: app:{}-stream:{}", streamPullPlay.getApp(), streamPullPlay.getStream()); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone();//深拷贝 String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.streamPullPlay(streamPullPlay, callback); return result; } /** * 停止拉流播放 * * @param streamPullPlay 拉流播放请求参数 * @return */ @Operation(summary = "停止拉流播放") @PostMapping("/stopStreamPullPlay") public AjaxResult stopStreamPullPlay(@RequestBody StreamPullPlay streamPullPlay) { mediaServerService.stopStreamPullPlay(streamPullPlay); return AjaxResult.success(); } /** * 获取截图 * * @param snap 截图参数 * @return */ @Operation(summary = "获取截图") @PostMapping("/getSnap") public AjaxResult getSnap(@RequestBody Snap snap) { ZlmMediaServer mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); if (mediaServer == null) { throw new RuntimeException("无可用的流媒体服务器"); } String filePath = mediaServerService.getSnap(mediaServer, snap); return AjaxResult.success(filePath); } /** * rtp播放 * * @param rtpServerParam 创建rtp端口请求参数 * @param request HttpServletRequest * @return */ @Operation(summary = "rtp播放") @PostMapping("/rtpPlay") public DeferredResult> rtpPlay(@RequestBody RTPServerParam rtpServerParam, HttpServletRequest request) { log.info("rtp播放: app:{}-stream:{}", rtpServerParam.getApp(), rtpServerParam.getStreamId()); if (!(LiveStreamType.HIK_SDK.getCode().equals(rtpServerParam.getType()) || LiveStreamType.HIK_ISUP.getCode().equals(rtpServerParam.getType()) || LiveStreamType.DAHUA_SDK.getCode().equals(rtpServerParam.getType()) )) { log.error("不支持的播放类型:{}", rtpServerParam.getType()); throw new RuntimeException("不支持的播放类型"); } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[rtp播放等待超时] app:{}, stream:{}", rtpServerParam.getApp(), rtpServerParam.getStreamId()); R wvpResult = R.fail(); wvpResult.setMsg("rtp播放超时"); result.setResult(wvpResult); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, rtpServerParam.getId()); mediaServerService.stopRtpPlay(rtpServerParam); }); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone();//深拷贝 String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.rtpPlay(rtpServerParam, callback); return result; } /** * 停止rtp播放 * * @param rtpServerParam 创建rtp端口请求参数 * @return */ @Operation(summary = "停止rtp播放") @PostMapping("/stopRtpPlay") public AjaxResult stopRtpPlay(@RequestBody RTPServerParam rtpServerParam) { log.info("停止rtp播放: id:{}", rtpServerParam.getId()); if (!(LiveStreamType.HIK_SDK.getCode().equals(rtpServerParam.getType()) || LiveStreamType.HIK_ISUP.getCode().equals(rtpServerParam.getType()) || LiveStreamType.DAHUA_SDK.getCode().equals(rtpServerParam.getType()) )) { log.error("不支持的播放类型:{}", rtpServerParam.getType()); throw new RuntimeException("不支持的播放类型"); } mediaServerService.stopRtpPlay(rtpServerParam); return AjaxResult.success(); } /** * 加载文件形成播放地址 * * @param id 设备id * @return */ @GetMapping("/loadRecord/{id}") public DeferredResult> loadRecord(@PathVariable Long id, HttpServletRequest request) { DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[加载录像文件超时] id={}", id); R wvpResult = R.fail(); wvpResult.setMsg("加载录像文件超时"); result.setResult(wvpResult); }); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone();//深拷贝 String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.loadRecord(id, callback); return result; } /** * 关闭流文件形成播放地址 * * @param id 加载文件参数 * @return */ @GetMapping("/closeStreams/{id}") public AjaxResult closeStreams(@PathVariable Long id) { mediaServerService.closeStreams(id); return AjaxResult.success(); } /** * 获取流媒体服务器列表 * * @return */ @Operation(summary = "获取流媒体服务器列表") @GetMapping(value = "/list") public AjaxResult getMediaServerList() { List list = mediaServerService.getAll(); return AjaxResult.success(list); } /** * 移除流媒体服务 * * @param id 流媒体ID */ @Operation(summary = "移除流媒体服务") @DeleteMapping(value = "/delete/{id}") public AjaxResult deleteMediaServer(@PathVariable("id") String id) { ZlmMediaServer mediaServer = mediaServerService.getOne(id); if (mediaServer == null) { throw new RuntimeException("流媒体不存在"); } mediaServerService.delete(mediaServer); return AjaxResult.success(); } /** * 保存流媒体服务 * * @param mediaServer 流媒体信息 */ @Operation(summary = "保存流媒体服务") @PostMapping(value = "/save") public AjaxResult saveMediaServer(@RequestBody ZlmMediaServer mediaServer) { ZlmMediaServer mediaServerItemInDatabase = mediaServerService.getOneFromDatabase(mediaServer.getId()); if (mediaServerItemInDatabase != null) { mediaServerService.update(mediaServer); } else { mediaServerService.add(mediaServer); // 发送事件 MediaServerChangeEvent event = new MediaServerChangeEvent(this); event.setMediaServerItemList(mediaServer); applicationEventPublisher.publishEvent(event); } return AjaxResult.success(); } /** * 测试流媒体服务 * * @param ip 流媒体服务IP * @param port 流媒体服务HTT端口 * @param secret 流媒体服务secret * @param type 流媒体服务类型 * @return */ @Operation(summary = "测试流媒体服务") @GetMapping(value = "/check") public AjaxResult checkMediaServer(@RequestParam String ip, @RequestParam int port, @RequestParam String secret, @RequestParam String type) { ZlmMediaServer mediaServer = mediaServerService.checkMediaServer(ip, port, secret, type); return AjaxResult.success(mediaServer); } /** * 获取流媒体服务 * * @param id 流媒体服务ID * @return */ @Operation(summary = "获取流媒体服务") @GetMapping(value = "/one/{id}") public AjaxResult getMediaServer(@PathVariable String id) { ZlmMediaServer mediaServer = mediaServerService.getOne(id); return AjaxResult.success(mediaServer); } /** * 获取流信息 * * @param app 应用名 * @param stream 流ID * @param mediaServerId 流媒体ID * @return */ @Operation(summary = "获取流信息") @GetMapping(value = "/media_info") public AjaxResult getMediaInfo(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId) { Assert.hasText(app, "app参数不能为空"); Assert.hasText(stream, "stream参数不能为空"); Assert.hasText(mediaServerId, "mediaServerId参数不能为空"); ZlmMediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new RuntimeException("流媒体不存在"); } return AjaxResult.success(mediaServerService.getMediaInfo(mediaServer, app, stream)); } /** * 重启流媒体 * * @param mediaServerId 流媒体ID * @return */ @Operation(summary = "重启流媒体") @GetMapping(value = "/restartServer/{mediaServerId}") public AjaxResult restartServer(@PathVariable String mediaServerId) { ZlmMediaServer mediaServer = mediaServerService.getOne(mediaServerId); if (mediaServer == null) { throw new RuntimeException("流媒体不存在"); } mediaServerService.restartServer(mediaServer); return AjaxResult.success(); } /** * 获取所有在线媒体服务器 * * @return */ @Operation(summary = "获取所有在线媒体服务器") @GetMapping(value = "/getAllOnlineMediaServe") public AjaxResult getAllOnlineMediaServe() { return AjaxResult.success(mediaServerService.getAllOnlineMediaServe()); } /** * 生成推流地址 * * @return */ @Operation(summary = "生成推流地址") @GetMapping(value = "/getStreamPushAddress/{id}") public AjaxResult getStreamPushAddress(@PathVariable Long id, String callId) { if (StringUtils.isEmpty(callId)) { return AjaxResult.error("callId不能是空"); } return AjaxResult.success(mediaServerService.getStreamPushAddress(id, callId)); } /** * 推流播放 * * @param request * @param id * @return */ @Operation(summary = "推流播放") @GetMapping(value = "/streamPullPush") public DeferredResult> streamPullPush(HttpServletRequest request, @RequestParam Long id) { Assert.notNull(id, "设备ID不可为NULL"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[等待推流超时] id={}", id); R fail = R.fail("等待推流超时"); result.setResult(fail); }); mediaServerService.streamPullPush(id, (code, msg, streamInfo) -> { if (code == 0 && streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone();//深拷贝 String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } R success = R.ok(new StreamContent(streamInfo)); result.setResult(success); } else { // 处理失败情况 log.info("[等待推流失败] id={}, code={}, msg={}", id, code, msg); R fail = R.fail(code, msg); result.setResult(fail); } }); return result; } /** * gb28181 播放 * * @param request * @param id 设备id * @return */ @Operation(summary = "gb28181播放") @GetMapping("/startGb28181Play/{id}") public DeferredResult> startGb28181Play( HttpServletRequest request, @PathVariable Long id ) { log.info("[gb28181 开始点播] id:{} ", id); Assert.notNull(id, "设备id"); R qsDevicer = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER); if (qsDevicer.getCode() != Constants.SUCCESS) { throw new RuntimeException("获取设备信息失败 id:" + id); } Assert.notNull(qsDevicer.getData(), "设备不存在 id:" + id); StreamChannel streamChannel = qsDevicer.getData(); if (!Boolean.TRUE.equals(streamChannel.getDeviceOnLine())) { throw new RuntimeException("设备不在线 id:" + id); } R deviceR = remoteGb28181Service.getDeviceByDeviceId(streamChannel.getGbDeviceId(), SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 id:" + streamChannel.getGbDeviceId()); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 id:" + streamChannel.getGbDeviceId()); if (!deviceR.getData().isOnLine()) { throw new RuntimeException("gb28181 国标设备不在线失败 id:" + streamChannel.getGbDeviceId()); } R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(streamChannel.getGbDeviceId(), streamChannel.getGbChannelId(), SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); } Assert.notNull(deviceChannelR.getData(), "gb28181 获取设备通道失败不存在 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); if (!"ON".equals(deviceChannelR.getData().getStatus())) { throw new RuntimeException("gb28181 国标设备通道不在线失败 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[点播等待超时] gbDeviceId:{}, gbChannelId:{}, ", streamChannel.getGbDeviceId(), streamChannel.getGbChannelId()); // 释放rtpserver R wvpResult = R.fail(); wvpResult.setMsg("点播超时"); result.setResult(wvpResult); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId()); mediaServerService.stopGb28181Play(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); }); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone();//深拷贝 String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; streamChannel.setStreamMode(deviceR.getData().getStreamMode()); mediaServerService.startGb28181Play(streamChannel, deviceR.getData(), callback); return result; } /** * gb28181 停止点播 * * @param id 设备id * @return */ @GetMapping("/stopGb28181Play/{id}") public AjaxResult stopGb28181Play(@PathVariable Long id) { log.info("[gb28181 停止点播] id:{} ", id); Assert.notNull(id, "设备id"); R qsDevicer = zlmStreamService.getQsDeviceInfo(id, SecurityConstants.INNER); if (qsDevicer.getCode() != Constants.SUCCESS) { throw new RuntimeException("获取设备信息失败 id:" + id); } Assert.notNull(qsDevicer.getData(), "设备不存在 id:" + id); StreamChannel streamChannel = qsDevicer.getData(); if (!Boolean.TRUE.equals(streamChannel.getDeviceOnLine())) { throw new RuntimeException("设备不在线 id:" + id); } R deviceR = remoteGb28181Service.getDeviceByDeviceId(streamChannel.getGbDeviceId(), SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 id:" + streamChannel.getGbDeviceId()); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 id:" + streamChannel.getGbDeviceId()); if (!deviceR.getData().isOnLine()) { throw new RuntimeException("gb28181 国标设备不在线失败 id:" + streamChannel.getGbDeviceId()); } R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(streamChannel.getGbDeviceId(), streamChannel.getGbChannelId(), SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); } Assert.notNull(deviceChannelR.getData(), "gb28181 获取设备通道失败不存在 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); if (!"ON".equals(deviceChannelR.getData().getStatus())) { throw new RuntimeException("gb28181 国标设备通道不在线失败 gbDeviceId:" + streamChannel.getGbDeviceId() + ",gbChannelId:" + streamChannel.getGbChannelId()); } mediaServerService.stopGb28181Play(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); JSONObject json = new JSONObject(); json.put("deviceId", streamChannel.getGbDeviceId()); json.put("channelId", streamChannel.getGbChannelId()); return AjaxResult.success(json); } /** * gb28181 播放(基于GbDevice,不依赖QS,按国标编码播放) * * @param request * @param gbDeviceId 国标设备编码 * @param gbChannelId 国标通道编码 * @return */ @Operation(summary = "gb28181播放(按国标编码)") @GetMapping("/startGb28181PlayByGbDeviceId/{gbDeviceId}/{gbChannelId}") public DeferredResult> startGb28181PlayByGbDeviceId( HttpServletRequest request, @PathVariable String gbDeviceId, @PathVariable String gbChannelId ) { log.info("[gb28181 开始点播] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId); Assert.hasText(gbDeviceId, "国标设备编码不可为空"); Assert.hasText(gbChannelId, "国标通道编码不可为空"); // 1. 查询 GbChannel 配置 R channelR = remoteGb28181Service.getGbChannel(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("获取GbChannel配置失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId + "。设备注册后会自动创建,请确认设备已上线并完成目录同步。"); } Assert.notNull(channelR.getData(), "GbChannel不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); StreamChannel streamChannel = ZlmStreamService.toStreamChannel(channelR.getData()); // 2. 查询国标设备 R deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); if (!deviceR.getData().isOnLine()) { throw new RuntimeException("gb28181 国标设备不在线 deviceId:" + gbDeviceId); } // 3. 查询国标通道 R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); if (!"ON".equals(deviceChannelR.getData().getStatus())) { throw new RuntimeException("gb28181 国标设备通道不在线 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[点播等待超时] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId); R fail = R.fail(); fail.setMsg("点播超时"); result.setResult(fail); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId()); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); }); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone(); String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.startGb28181PlayByGbChannel(streamChannel, deviceR.getData(), callback); return result; } /** * gb28181 停止点播(基于GbDevice,不依赖QS,按国标编码停止) * * @param gbDeviceId 国标设备编码 * @param gbChannelId 国标通道编码 * @return */ @Operation(summary = "gb28181停止点播(按国标编码)") @GetMapping("/stopGb28181PlayByGbDeviceId/{gbDeviceId}/{gbChannelId}") public AjaxResult stopGb28181PlayByGbDeviceId( @PathVariable String gbDeviceId, @PathVariable String gbChannelId ) { log.info("[gb28181 停止点播] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId); Assert.hasText(gbDeviceId, "国标设备编码不可为空"); Assert.hasText(gbChannelId, "国标通道编码不可为空"); // 1. 查询 GbChannel 配置 R channelR = remoteGb28181Service.getGbChannel(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS || channelR.getData() == null) { throw new RuntimeException("GbChannel不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } StreamChannel streamChannel = ZlmStreamService.toStreamChannel(channelR.getData()); // 2. 查询国标设备 R deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); // 3. 查询国标通道 R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); JSONObject json = new JSONObject(); json.put("gbDeviceId", gbDeviceId); json.put("gbChannelId", gbChannelId); return AjaxResult.success(json); } /** * gb28181 播放(基于GbChannel主键ID) * * @param request * @param channelId GbChannel主键ID * @return */ @Operation(summary = "gb28181播放(按通道主键ID)") @GetMapping("/startGb28181PlayByChannelId/{channelId}") public DeferredResult> startGb28181PlayByChannelId( HttpServletRequest request, @PathVariable Long channelId ) { log.info("[gb28181 开始点播] channelId:{}", channelId); Assert.notNull(channelId, "通道ID不可为空"); // 1. 根据主键ID查询 GbChannel R channelR = remoteGb28181Service.getGbChannelById(channelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("获取GbChannel失败 channelId:" + channelId); } Assert.notNull(channelR.getData(), "GbChannel不存在 channelId:" + channelId); StreamChannel streamChannel = ZlmStreamService.toStreamChannel(channelR.getData()); String gbDeviceId = streamChannel.getGbDeviceId(); String gbChannelId = streamChannel.getGbChannelId(); // 2. 查询国标设备 R deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); if (!deviceR.getData().isOnLine()) { throw new RuntimeException("gb28181 国标设备不在线 deviceId:" + gbDeviceId); } // 3. 查询国标通道 R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); if (!"ON".equals(deviceChannelR.getData().getStatus())) { throw new RuntimeException("gb28181 国标设备通道不在线 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[点播等待超时] channelId:{}, gbDeviceId:{}, gbChannelId:{}", channelId, gbDeviceId, gbChannelId); R fail = R.fail(); fail.setMsg("点播超时"); result.setResult(fail); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, streamChannel.getId()); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); }); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone(); String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.startGb28181PlayByGbChannel(streamChannel, deviceR.getData(), callback); return result; } /** * gb28181 停止点播(基于GbChannel主键ID) * * @param channelId GbChannel主键ID * @return */ @Operation(summary = "gb28181停止点播(按通道主键ID)") @GetMapping("/stopGb28181PlayByChannelId/{channelId}") public AjaxResult stopGb28181PlayByChannelId(@PathVariable Long channelId) { log.info("[gb28181 停止点播] channelId:{}", channelId); Assert.notNull(channelId, "通道ID不可为空"); // 1. 根据主键ID查询 GbChannel R channelR = remoteGb28181Service.getGbChannelById(channelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS || channelR.getData() == null) { throw new RuntimeException("GbChannel不存在 channelId:" + channelId); } StreamChannel streamChannel = ZlmStreamService.toStreamChannel(channelR.getData()); String gbDeviceId = streamChannel.getGbDeviceId(); String gbChannelId = streamChannel.getGbChannelId(); // 2. 查询国标设备 R deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); // 3. 查询国标通道 R deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, streamChannel, deviceR.getData(), streamChannel.getDeviceCode()); JSONObject json = new JSONObject(); json.put("channelId", channelId); json.put("gbDeviceId", gbDeviceId); json.put("gbChannelId", gbChannelId); return AjaxResult.success(json); } /** * 拉流代理(通过通道ID,简化传参) * * @param request HttpServletRequest * @param channelId 通道ID * @return */ @Operation(summary = "拉流代理(通过通道ID,简化传参)") @PostMapping("/streamPullPlayByChannelId/{channelId}") public DeferredResult> streamPullPlayByChannelId( HttpServletRequest request, @PathVariable String channelId ) { log.info("[拉流代理-通道ID] channelId:{}", channelId); Assert.hasText(channelId, "通道ID不可为空"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); ErrorCallback callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone(); String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.streamPullPlayByChannelId(channelId, callback); return result; } /** * 停止拉流代理(通过通道ID) * * @param channelId 通道ID * @return */ @Operation(summary = "停止拉流代理(通过通道ID)") @PostMapping("/stopStreamPullPlayByChannelId/{channelId}") public AjaxResult stopStreamPullPlayByChannelId(@PathVariable String channelId) { log.info("[停止拉流代理-通道ID] channelId:{}", channelId); Assert.hasText(channelId, "通道ID不可为空"); mediaServerService.stopStreamPullPlayByChannelId(channelId); return AjaxResult.success(); } }