Commit c511a82d15c3cac2d632004bbc748fb7d99b687a

Authored by xp.Huang
1 parent 54c0123a

feat: 新增gbt28181(支持点播、云台控制)

Showing 80 changed files with 4464 additions and 48 deletions

Too many changes to show.

To preserve performance only 80 of 155 files are displayed.

... ... @@ -92,6 +92,10 @@
92 92 </dependency>
93 93 <dependency>
94 94 <groupId>org.thingsboard.common.transport</groupId>
  95 + <artifactId>gbt28181</artifactId>
  96 + </dependency>
  97 + <dependency>
  98 + <groupId>org.thingsboard.common.transport</groupId>
95 99 <artifactId>http</artifactId>
96 100 </dependency>
97 101 <dependency>
... ...
... ... @@ -74,7 +74,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
74 74
75 75 //Thingskit function
76 76 public static final String CODE_BASED_LOGIN_ENTRY_POINT = "/api/yt/auth/code/login";
77   - public static final String[] YT_NOT_AUTH_API = new String[]{"/api/yt/auth/code/login","/api/yt/third/bind","/api/yt/third/login/*","/api/yt/third/login/id/*", "/api/yt/third/authorize","/api/yt/platform/get","/api/yt/app_design/get", "/api/yt/noauth/**"};
  77 + public static final String[] YT_NOT_AUTH_API = new String[]{"/api/yt/auth/code/login","/api/yt/third/bind","/api/yt/third/login/*","/api/yt/third/login/id/*", "/api/yt/third/authorize","/api/yt/platform/get","/api/yt/app_design/get", "/api/yt/noauth/**","/api/index/hook/**"};
78 78
79 79 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
80 80 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
... ...
... ... @@ -82,7 +82,7 @@ public class ThingsKitExceptionHandler {
82 82 SecurityUser currentUser = getCurrentUser();
83 83
84 84 Asset entity = new Asset();
85   - entity.setName(e.getMessage());
  85 + entity.setName(e.getClass().getName());
86 86
87 87 // 请求相关信息
88 88 SysLogOperateDTO additionalInfo = new SysLogOperateDTO();
... ...
  1 +package org.thingsboard.server.controller.yunteng;
  2 +
  3 +import io.swagger.annotations.Api;
  4 +import io.swagger.annotations.ApiParam;
  5 +import lombok.RequiredArgsConstructor;
  6 +import org.springframework.security.access.prepost.PreAuthorize;
  7 +import org.springframework.web.bind.annotation.GetMapping;
  8 +import org.springframework.web.bind.annotation.RequestMapping;
  9 +import org.springframework.web.bind.annotation.RequestParam;
  10 +import org.springframework.web.bind.annotation.RestController;
  11 +import org.thingsboard.server.common.data.StringUtils;
  12 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  13 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  14 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  15 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  16 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  17 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
  18 +import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
  19 +import org.thingsboard.server.controller.BaseController;
  20 +import org.thingsboard.server.dao.yunteng.mapper.DeviceMapper;
  21 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  22 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
  23 +
  24 +import java.util.HashMap;
  25 +
  26 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
  27 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE;
  28 +
  29 +@RestController
  30 +@RequestMapping("api/yt/video/channel")
  31 +@Api(tags = {"视频通道"})
  32 +@RequiredArgsConstructor
  33 +public class TkVideoChannelController extends BaseController {
  34 +
  35 + private final TkVideoChannelService tkVideoChannelService;
  36 + private final TkDeviceService tkDeviceService;
  37 + private final DeviceMapper deviceMapper;
  38 +
  39 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  40 + @GetMapping(params = {PAGE_SIZE, PAGE})
  41 + public TkPageData<VideoChanelDTO> pageData(
  42 + @RequestParam(PAGE_SIZE) int pageSize,
  43 + @RequestParam(PAGE) int page,
  44 + @RequestParam(value = ORDER_FILED, required = false) String orderBy,
  45 + @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType,
  46 + @ApiParam(value = "通道所属设备ID(tbDeviceId)")
  47 + @RequestParam(value = "tbDeviceId") String tbDeviceId,
  48 + @ApiParam(value = "视频通道名称")
  49 + @RequestParam(value = "name", required = false) String name,
  50 + @ApiParam(value = "国标编号")
  51 + @RequestParam(value = "cameraCode", required = false) String cameraCode) throws ThingsboardException {
  52 + HashMap<String, Object> queryMap = new HashMap<>();
  53 + if(StringUtils.isNotEmpty(name)){
  54 + queryMap.put("name",name);
  55 + }
  56 + if(StringUtils.isNotEmpty(cameraCode)){
  57 + queryMap.put("cameraCode",cameraCode);
  58 + }
  59 + String tenantId = getCurrentUser().getCurrentTenantId();
  60 + if(StringUtils.isEmpty(tbDeviceId)){
  61 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  62 + }
  63 + if(getCurrentUser().isCustomerUser()){
  64 + DeviceDTO deviceDTO = deviceMapper.findDeviceByTbDeviceIdAndCustomerId(
  65 + tenantId,tbDeviceId,getCurrentUser().getCustomerId().toString());
  66 + if(null == deviceDTO){
  67 + throw new TkDataValidationException(ErrorMessage.NOT_BELONG_CURRENT_CUSTOMER.getMessage());
  68 + }
  69 + }
  70 + queryMap.put("tbDeviceId",tbDeviceId);
  71 + queryMap.put(PAGE_SIZE, pageSize);
  72 + queryMap.put(PAGE, page);
  73 + queryMap.put(ORDER_FILED, orderBy);
  74 + if (orderType != null) {
  75 + queryMap.put(ORDER_TYPE, orderType.name());
  76 + }
  77 + return tkVideoChannelService.page(tenantId, queryMap);
  78 + }
  79 +}
... ...
... ... @@ -11,6 +11,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
11 11 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
12 12 import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
13 13 import org.thingsboard.server.common.data.yunteng.dto.TkVideoDTO;
  14 +import org.thingsboard.server.common.data.yunteng.dto.TkVideoGptDTO;
14 15 import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
15 16 import org.thingsboard.server.common.data.yunteng.utils.tools.ProtocolType;
16 17 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
... ... @@ -68,6 +69,17 @@ public class TkVideoController extends BaseController {
68 69 return videoService.saveOrUpdate(dto);
69 70 }
70 71
  72 + @PostMapping("/addGpt")
  73 + @ApiOperation("新增国标视频")
  74 + @PreAuthorize(
  75 + "@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:post','api:yt:video:update'})")
  76 + public TkVideoGptDTO saveOrUpdateAlarmProfile(@Validated @RequestBody TkVideoGptDTO dto)
  77 + throws ThingsboardException {
  78 + dto.setTenantId(getCurrentUser().getCurrentTenantId());
  79 + return videoService.saveGpt(dto);
  80 + }
  81 +
  82 +
71 83 @DeleteMapping
72 84 @ApiOperation("删除")
73 85 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:delete'})")
... ...
  1 +package org.thingsboard.server.controller.yunteng.media;
  2 +
  3 +import com.google.common.util.concurrent.MoreExecutors;
  4 +import io.swagger.annotations.Api;
  5 +import io.swagger.annotations.ApiOperation;
  6 +import io.swagger.annotations.ApiParam;
  7 +import io.swagger.v3.oas.annotations.Parameter;
  8 +import lombok.RequiredArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.apache.curator.shaded.com.google.common.util.concurrent.FutureCallback;
  11 +import org.apache.curator.shaded.com.google.common.util.concurrent.Futures;
  12 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  13 +import org.checkerframework.checker.nullness.qual.Nullable;
  14 +import org.jetbrains.annotations.NotNull;
  15 +import org.springframework.web.bind.annotation.*;
  16 +import org.springframework.web.context.request.async.DeferredResult;
  17 +import org.thingsboard.server.common.data.StringUtils;
  18 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  19 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  20 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  21 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  22 +import org.thingsboard.server.common.data.yunteng.dto.sip.StreamContentDTO;
  23 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  24 +import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
  25 +import org.thingsboard.server.controller.BaseController;
  26 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  27 +import org.thingsboard.server.service.yunteng.media.TkVideoControlService;
  28 +
  29 +@RestController
  30 +@RequestMapping("api/yt/video/control")
  31 +@Api(tags = {"视频控制管理"})
  32 +@RequiredArgsConstructor
  33 +@Slf4j
  34 +public class TkVideoControlController extends BaseController {
  35 +
  36 + private final TkVideoControlService tkVideoControlService;
  37 + private final TkDeviceService tkDeviceService;
  38 +
  39 + @GetMapping("/start/{deviceId}/{channelId}")
  40 + @ApiOperation(value = "视频点播/预览")
  41 + public DeferredResult<ResponseResult<StreamContentDTO>> startPlay(
  42 + @PathVariable("deviceId") String tbDeviceId, @PathVariable("channelId") String channelId)
  43 + throws ThingsboardException {
  44 + if (StringUtils.isEmpty(tbDeviceId) || StringUtils.isEmpty(channelId)) {
  45 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  46 + }
  47 + DeferredResult<ResponseResult<StreamContentDTO>> response = new DeferredResult<>();
  48 + ListenableFuture<StreamContentDTO> future =
  49 + tkVideoControlService.startPlay(getCurrentUser(),tbDeviceId, channelId, getCurrentUser().getCurrentTenantId());
  50 + Futures.addCallback(
  51 + future,
  52 + new FutureCallback<>() {
  53 + @Override
  54 + public void onSuccess(@Nullable StreamContentDTO streamContentDTO) {
  55 + response.setResult(ResponseResult.success(streamContentDTO));
  56 + }
  57 +
  58 + @Override
  59 + public void onFailure(@NotNull Throwable throwable) {
  60 + response.setResult(ResponseResult.failed(null));
  61 + }
  62 + },
  63 + MoreExecutors.directExecutor());
  64 + return response;
  65 + }
  66 +
  67 + @ApiOperation(value = "停止点播")
  68 + @GetMapping("/stop/{deviceId}/{channelId}")
  69 + public ResponseResult playStop(
  70 + @ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String tbDeviceId,
  71 + @ApiParam(value = "通道ID", required = true) @PathVariable("channelId") String channelId)
  72 + throws ThingsboardException {
  73 + if (StringUtils.isEmpty(tbDeviceId) || StringUtils.isEmpty(channelId)) {
  74 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  75 + }
  76 + return ResponseResult.success(
  77 + tkVideoControlService.stopPlay(getCurrentUser(),tbDeviceId, channelId));
  78 + }
  79 +
  80 + @ApiOperation(value = "云台控制")
  81 + @Parameter(name = "tbDeviceId", description = "TB设备ID", required = true)
  82 + @Parameter(name = "channelId", description = "通道国标编号", required = true)
  83 + @Parameter(
  84 + name = "command",
  85 + description =
  86 + "控制指令,允许值: LEFT, RIGHT, UP, DOWN, UP_LEFT, UPRIGHT, DOWN_LEFT, DOWN_RIGHT, ZOOM_IN, ZOOM_OUT, STOP",
  87 + required = true)
  88 + @Parameter(name = "horizonSpeed", description = "水平速度", required = true)
  89 + @Parameter(name = "verticalSpeed", description = "垂直速度", required = true)
  90 + @Parameter(name = "zoomSpeed", description = "缩放速度", required = true)
  91 + @GetMapping("/control/{tbDeviceId}/{channelId}")
  92 + public void ptzControl(
  93 + @PathVariable String tbDeviceId,
  94 + @PathVariable String channelId,
  95 + PTZCommandEnum command,
  96 + int horizonSpeed,
  97 + int verticalSpeed,
  98 + int zoomSpeed)
  99 + throws ThingsboardException {
  100 + ResponseResult.success(
  101 + tkVideoControlService.control(getCurrentUser(),tbDeviceId, channelId,command,horizonSpeed,verticalSpeed,zoomSpeed));
  102 + }
  103 +}
... ...
  1 +package org.thingsboard.server.controller.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import io.swagger.annotations.Api;
  5 +import io.swagger.annotations.ApiOperation;
  6 +import lombok.RequiredArgsConstructor;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.web.bind.annotation.*;
  9 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  10 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  11 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  12 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  13 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.*;
  14 +import org.thingsboard.server.controller.BaseController;
  15 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  16 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerService;
  17 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
  18 +import org.thingsboard.server.service.yunteng.media.TkVideoControlService;
  19 +
  20 +/** ZLMediaServer的hook事件监听 */
  21 +@RestController
  22 +@RequestMapping("api/index/hook")
  23 +@Api(tags = {"ZLMediaServer的hook事件监听"})
  24 +@RequiredArgsConstructor
  25 +@Slf4j
  26 +public class ZLMediaKitHookController extends BaseController {
  27 + private final TkMediaServerService tkMediaServerService;
  28 + private final TkVideoChannelService tkVideoChannelService;
  29 + private final TkVideoControlService controlService;
  30 +
  31 + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
  32 + @ApiOperation(value = "保持流媒体服务器的心跳")
  33 + public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
  34 + // log.error("ZLMediaKitHook保持流媒体服务器的心跳【on_server_keepalive】,API参数【{}】",
  35 + // JacksonUtil.toString(param));
  36 + return tkMediaServerService.zlmOnServerKeepalive(param);
  37 + }
  38 +
  39 + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
  40 + @ApiOperation(value = "播放器鉴权事件")
  41 + public HookResult onPlay(@RequestBody BaseParam param) {
  42 + return tkMediaServerService.zlmOnPlay(param);
  43 + }
  44 +
  45 + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
  46 + @ApiOperation(value = "rtsp/rtmp/rtp推流鉴权事件")
  47 + public HookResultForOnPublish onPublish(@RequestBody BaseParam param) {
  48 + return tkMediaServerService.onPublish(param);
  49 + }
  50 +
  51 + @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
  52 + @ApiOperation(value = "rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感")
  53 + public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
  54 + return tkMediaServerService.zlmOnStreamChanged(param);
  55 + }
  56 +
  57 + @ResponseBody
  58 + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
  59 + @ApiOperation(value = "流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流")
  60 + public JsonNode onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) throws ThingsboardException {
  61 + JsonNode result = tkMediaServerService.zlmOnStreamNoneReader(param);
  62 + if(result.has(FastIotConstants.ZLMediaBody.CHANNEL_ID) &&result.has(FastIotConstants.ZLMediaBody.CAMERA_CODE)){
  63 + String channelId = result.get(FastIotConstants.ZLMediaBody.CHANNEL_ID).asText();
  64 + String cameraCode = result.get(FastIotConstants.ZLMediaBody.CAMERA_CODE).asText();
  65 + VideoChanelDTO chanelDTO = tkVideoChannelService.findVideoChannelById(cameraCode,channelId, null);
  66 + controlService.byeCmdInSsrcTransaction(chanelDTO.getTenantId(),
  67 + false,
  68 + chanelDTO.getDeviceId(),
  69 + cameraCode,channelId,result.get(FastIotConstants.ZLMediaBody.SSRCINFO_STREAM).asText(),
  70 + fromDeviceRpcResponse->{
  71 +
  72 + });
  73 + tkVideoChannelService.updateVideoChannelStreamId(null, cameraCode, channelId);//storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  74 + }
  75 + return result;
  76 + }
  77 +}
... ...
... ... @@ -15,6 +15,9 @@
15 15 */
16 16 package org.thingsboard.server.service.transport;
17 17
  18 +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
  19 +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
  20 +
18 21 import com.fasterxml.jackson.core.JsonProcessingException;
19 22 import com.fasterxml.jackson.databind.JsonNode;
20 23 import com.fasterxml.jackson.databind.ObjectMapper;
... ... @@ -23,8 +26,18 @@ import com.google.common.util.concurrent.Futures;
23 26 import com.google.common.util.concurrent.ListenableFuture;
24 27 import com.google.common.util.concurrent.MoreExecutors;
25 28 import com.google.protobuf.ByteString;
  29 +import java.util.List;
  30 +import java.util.Optional;
  31 +import java.util.Set;
  32 +import java.util.UUID;
  33 +import java.util.concurrent.ConcurrentHashMap;
  34 +import java.util.concurrent.ConcurrentMap;
  35 +import java.util.concurrent.locks.Lock;
  36 +import java.util.concurrent.locks.ReentrantLock;
  37 +import java.util.stream.Collectors;
26 38 import lombok.RequiredArgsConstructor;
27 39 import lombok.extern.slf4j.Slf4j;
  40 +import org.apache.zookeeper.Op;
28 41 import org.springframework.stereotype.Service;
29 42 import org.thingsboard.common.util.JacksonUtil;
30 43 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
... ... @@ -60,9 +73,14 @@ import org.thingsboard.server.common.data.page.PageLink;
60 73 import org.thingsboard.server.common.data.relation.EntityRelation;
61 74 import org.thingsboard.server.common.data.security.DeviceCredentials;
62 75 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  76 +import org.thingsboard.server.common.data.yunteng.common.media.VideoStreamSessionManager;
  77 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
63 78 import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
64 79 import org.thingsboard.server.common.data.yunteng.dto.TkDeviceScriptDTO;
65   -import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType;
  80 +import org.thingsboard.server.common.data.yunteng.dto.sip.SipDeviceDTO;
  81 +import org.thingsboard.server.common.data.yunteng.dto.sip.SsrcTransactionDTO;
  82 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  83 +import org.thingsboard.server.common.data.yunteng.enums.VideoCmdEnum;
66 84 import org.thingsboard.server.common.msg.EncryptionUtil;
67 85 import org.thingsboard.server.common.msg.TbMsg;
68 86 import org.thingsboard.server.common.msg.TbMsgDataType;
... ... @@ -79,6 +97,8 @@ import org.thingsboard.server.dao.relation.RelationService;
79 97 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
80 98 import org.thingsboard.server.dao.yunteng.service.TkDeviceScriptService;
81 99 import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  100 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerNodeService;
  101 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
82 102 import org.thingsboard.server.gen.transport.TransportProtos;
83 103 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
84 104 import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
... ... @@ -105,19 +125,6 @@ import org.thingsboard.server.service.install.DefaultSystemDataLoaderService;
105 125 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
106 126 import org.thingsboard.server.service.resource.TbResourceService;
107 127
108   -import java.util.Arrays;
109   -import java.util.List;
110   -import java.util.Optional;
111   -import java.util.UUID;
112   -import java.util.concurrent.ConcurrentHashMap;
113   -import java.util.concurrent.ConcurrentMap;
114   -import java.util.concurrent.locks.Lock;
115   -import java.util.concurrent.locks.ReentrantLock;
116   -import java.util.stream.Collectors;
117   -
118   -import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
119   -import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
120   -
121 128 /**
122 129 * Created by ashvayka on 05.10.18.
123 130 */
... ... @@ -135,6 +142,10 @@ public class DefaultTransportApiService implements TransportApiService {
135 142 private final DeviceService deviceService;
136 143 private final TkDeviceService ytDeviceService;
137 144 private final TkDeviceScriptService scriptService;
  145 + private final TkVideoChannelService channelService;
  146 + private final TkMediaServerNodeService mediaServerNodeService;
  147 + private final VideoStreamSessionManager videoStreamSessionManager;
  148 +
138 149 private final RelationService relationService;
139 150 private final DeviceCredentialsService deviceCredentialsService;
140 151 private final DbCallbackExecutorService dbCallbackExecutorService;
... ... @@ -192,6 +203,9 @@ public class DefaultTransportApiService implements TransportApiService {
192 203 else if (transportApiRequestMsg.hasScript()) {
193 204 result = handle(transportApiRequestMsg.getScript());
194 205 }
  206 + else if (transportApiRequestMsg.hasGbt28181RequestMsg()) {
  207 + result = handleGbt(transportApiRequestMsg.getGbt28181RequestMsg());
  208 + }
195 209
196 210
197 211 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
... ... @@ -685,20 +699,74 @@ public class DefaultTransportApiService implements TransportApiService {
685 699 private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.ScriptProto requestMsg) {
686 700 List<TkDeviceScriptDTO> allScriptes = scriptService.getScriptes();
687 701 TransportApiResponseMsg.Builder responseBuilder = TransportApiResponseMsg.newBuilder();
688   - allScriptes.forEach(
689   - item -> {
690   - UUID tenantId = UUID.fromString(item.getTenantId());
691   - UUID id = UUID.fromString(item.getId());
692   - responseBuilder.addScriptsResponseMsg(
693   - TransportProtos.ScriptProto.newBuilder()
694   - .setConvertJs(item.getConvertJs())
695   - .setTenantIdLSB(tenantId.getLeastSignificantBits())
696   - .setTenantIdMSB(tenantId.getMostSignificantBits())
697   - .setScriptIdLSB(id.getLeastSignificantBits())
698   - .setScriptIdMSB(id.getMostSignificantBits())
699   - .setFunctionType(item.getScriptType().name())
700   - .setStatus(item.getStatus()));
701   - });
  702 + allScriptes.forEach(
  703 + item -> {
  704 + UUID tenantId = UUID.fromString(item.getTenantId());
  705 + UUID id = UUID.fromString(item.getId());
  706 + responseBuilder.addScriptsResponseMsg(
  707 + TransportProtos.ScriptProto.newBuilder()
  708 + .setConvertJs(item.getConvertJs())
  709 + .setTenantIdLSB(tenantId.getLeastSignificantBits())
  710 + .setTenantIdMSB(tenantId.getMostSignificantBits())
  711 + .setScriptIdLSB(id.getLeastSignificantBits())
  712 + .setScriptIdMSB(id.getMostSignificantBits())
  713 + .setFunctionType(item.getScriptType().name())
  714 + .setStatus(item.getStatus()));
  715 + });
702 716 return Futures.immediateFuture(responseBuilder.build());
703 717 }
  718 + private ListenableFuture<TransportApiResponseMsg> handleGbt(TransportProtos.Gbt28181RequestMsg requestMsg) {
  719 + TransportProtos.Gbt28181ResponseMsg.Builder responseMsgBuilder =TransportProtos.Gbt28181ResponseMsg.newBuilder();
  720 + String tenantId = new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()).toString();
  721 + String tbDeviceId = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()).toString();
  722 + String type = requestMsg.getContextType();
  723 + switch (VideoCmdEnum.valueOf(type)){
  724 + case DeviceInfo:
  725 + Optional<SipDeviceDTO> camera = dataDecodingEncodingService.decode(requestMsg.getContext().toByteArray());
  726 + camera.ifPresent(sip ->{
  727 + ytDeviceService.updateDeviceInfo(tenantId,tbDeviceId, FastIotConstants.DeviceAdditional.SIP,JacksonUtil.valueToTree(sip));
  728 + });
  729 +
  730 + break;
  731 + case Catalog:
  732 + Optional<List<VideoChanelDTO>> chanel = dataDecodingEncodingService.decode(requestMsg.getContext().toByteArray());
  733 + chanel.ifPresent(d->{
  734 + d.forEach(channelService::saveOrUpdate);
  735 + });
  736 + break;
  737 + default:
  738 + release(tenantId,tbDeviceId);
  739 +
  740 + }
  741 +// byte[] channaelMsgBytes = dataDecodingEncodingService.encode(channelDTO);
  742 +// responseMsgBuilder.setContext(ByteString.copyFrom(channaelMsgBytes));
  743 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setGbt28181ResponseMsg(responseMsgBuilder.build()).build());
  744 + }
  745 + private void release(String tenantId,String tbDeviceId){
  746 + DeviceDTO device = ytDeviceService.findDeviceInfoByTbDeviceId(tenantId,tbDeviceId);
  747 + Optional.ofNullable(device).ifPresent(dev->{
  748 + JsonNode sip = dev.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP);
  749 + if(!sip.isEmpty() && sip.has(FastIotConstants.ZLMediaBody.CAMERA_CODE)){
  750 + String cameraCode = sip.get(FastIotConstants.ZLMediaBody.CAMERA_CODE).asText();
  751 + Optional<Set<String>> ssrcKeys =
  752 + videoStreamSessionManager.getSsrcTransactionCamaraKey(cameraCode);
  753 + ssrcKeys.ifPresent(all ->{
  754 + all.forEach(fullKey->{
  755 + videoStreamSessionManager.getSsrcTransaction(fullKey).ifPresent(ssrcTransaction -> {
  756 + mediaServerNodeService.releaseSsrc(
  757 + ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
  758 + mediaServerNodeService.closeRTPServer(
  759 + ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
  760 + videoStreamSessionManager.remove(
  761 + cameraCode, ssrcTransaction.getChannelId(),
  762 + ssrcTransaction.getStream());
  763 + });
  764 +
  765 + });
  766 + });
  767 + }
  768 + });
  769 +
  770 +
  771 + }
704 772 }
... ...
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  5 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  6 +import org.thingsboard.server.common.data.yunteng.dto.sip.*;
  7 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  8 +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
  9 +import org.thingsboard.server.service.security.model.SecurityUser;
  10 +
  11 +import java.util.function.Consumer;
  12 +
  13 +public interface TkVideoControlService {
  14 +
  15 + /**
  16 + * 视频点播
  17 + *
  18 + * @param deviceId 设备ID
  19 + * @param channelId 通道ID
  20 + * @param tenantId 租户ID
  21 + * @return 视频流播放地址内容
  22 + */
  23 + ListenableFuture<StreamContentDTO> startPlay(
  24 + SecurityUser currentUser, String deviceId, String channelId, String tenantId);
  25 +
  26 + /**
  27 + * 当点播时的推送处理程序
  28 + *
  29 + * @param mediaServerItem 流媒体信息
  30 + * @param response 响应
  31 + * @param deviceId 点播设备ID
  32 + * @param channelId 点播设备通道ID
  33 + */
  34 + StreamInfoDTO onPublishHandlerForPlay(
  35 + MediaServerDTO mediaServerItem, JsonNode response, String deviceId, String channelId);
  36 +
  37 + /**
  38 + * 通过设备ID和通道ID暂停播放
  39 + *
  40 + * @param tbDeviceId TB设备的ID
  41 + * @param channelId 通道ID
  42 + * @return 暂停播放结果 true成功 false失败
  43 + */
  44 + boolean stopPlay(SecurityUser currentUser, String tbDeviceId, String channelId) throws ThingsboardException;
  45 +
  46 + /**
  47 + * 摄像头控制
  48 + * @param currentUser 登录用户
  49 + * @param tbDeviceId 设备ID
  50 + * @param channelId 设备通道
  51 + * @param command 控制指令
  52 + * @param horizonSpeed 水平速度
  53 + * @param verticalSpeed 垂直速度
  54 + * @param zoomSpeed 缩放速度
  55 + * @return
  56 + */
  57 + boolean control(SecurityUser currentUser, String tbDeviceId, String channelId, PTZCommandEnum command, int horizonSpeed, int verticalSpeed, int zoomSpeed) throws ThingsboardException;
  58 +
  59 + StreamInfoDTO play(
  60 + SecurityUser currentUser,
  61 + String tbDeviceId,
  62 + MediaServerDTO mediaServerItem,
  63 + SsrcInfoDTO ssrcInfo,
  64 + SipDeviceDTO device,
  65 + String channelId);
  66 + public void byeCmdInSsrcTransaction(
  67 + String tenantId,
  68 + boolean oneWay,
  69 + String tbDeviceId,
  70 + String cameraCode,String channelId,String streamId,
  71 + Consumer<FromDeviceRpcResponse> responseConsumer)
  72 + throws ThingsboardException;
  73 +}
... ...
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.node.ObjectNode;
  5 +import java.util.Optional;
  6 +import java.util.UUID;
  7 +import java.util.Vector;
  8 +import java.util.concurrent.CountDownLatch;
  9 +import java.util.concurrent.TimeUnit;
  10 +import java.util.concurrent.atomic.AtomicReference;
  11 +import java.util.function.Consumer;
  12 +import javax.sdp.*;
  13 +import lombok.RequiredArgsConstructor;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.apache.curator.shaded.com.google.common.util.concurrent.Futures;
  16 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  17 +import org.springframework.beans.factory.annotation.Value;
  18 +import org.springframework.stereotype.Service;
  19 +import org.thingsboard.server.common.data.StringUtils;
  20 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  21 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  25 +import org.thingsboard.server.common.data.yunteng.common.media.VideoStreamSessionManager;
  26 +import org.thingsboard.server.common.data.yunteng.common.media.ZlmHttpHookSubscribe;
  27 +import org.thingsboard.server.common.data.yunteng.config.media.UserSetting;
  28 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  29 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  30 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  31 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  32 +import org.thingsboard.server.common.data.yunteng.dto.sip.*;
  33 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.HookSubscribeForStreamChange;
  34 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  35 +import org.thingsboard.server.common.data.yunteng.enums.SessionTypeEnum;
  36 +import org.thingsboard.server.common.data.yunteng.enums.VideoMethodEnum;
  37 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  38 +import org.thingsboard.server.common.data.yunteng.utils.ZLMediaKitRestFulUtils;
  39 +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
  40 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  41 +import org.thingsboard.server.dao.util.yunteng.ZLMediaKitTaskUtils;
  42 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  43 +import org.thingsboard.server.dao.yunteng.service.media.*;
  44 +import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
  45 +import org.thingsboard.server.service.security.model.SecurityUser;
  46 +
  47 +@Service
  48 +@RequiredArgsConstructor
  49 +@Slf4j
  50 +public class TkVideoControlServiceImpl implements TkVideoControlService {
  51 +
  52 + private final TkMediaServerService tkMediaServerService;
  53 + private final TkMediaServerNodeService tkMediaServerNodeService;
  54 + private final TkDeviceService tkDeviceService;
  55 + private final TkVideoChannelService tkVideoChannelService;
  56 + private final TkCacheStorageService tkCacheStorageService;
  57 + private final ZLMediaKitTaskUtils zlMediaKitTaskUtils;
  58 + private final UserSetting userSetting;
  59 + private final ZlmHttpHookSubscribe subscribe;
  60 + private final VideoStreamSessionManager videoStreamSessionManager;
  61 + private final ZLMediaKitRestFulUtils zlMediaKitRestFulUtils;
  62 + private final TbCoreDeviceRpcService deviceRpcService;
  63 +
  64 + @Override
  65 + public ListenableFuture<StreamContentDTO> startPlay(
  66 + SecurityUser currentUser, String deviceId, String channelId, String tenantId) {
  67 + // 判断设备、通道是否存在
  68 + DeviceDTO deviceDTO = tkDeviceService.checkDeviceByTenantIdAndId(tenantId, deviceId, true);
  69 + SipDeviceDTO sipDeviceDTO =
  70 + JacksonUtil.convertValue(
  71 + deviceDTO.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP),
  72 + SipDeviceDTO.class);
  73 + // 获取设备的附加信息
  74 + if (null == sipDeviceDTO) {
  75 + throw new TkDataValidationException(ErrorMessage.FOUND_VIDEO_DEVICE_FAILED.getMessage());
  76 + }
  77 + VideoChanelDTO videoChanelDTO = tkVideoChannelService.findVideoChannelById(sipDeviceDTO.getCameraCode(), channelId, tenantId);
  78 + if (null == videoChanelDTO) {
  79 + throw new TkDataValidationException(ErrorMessage.VIDEO_CHANNEL_NOT_FOUND.getMessage());
  80 + }
  81 + // 找到可以使用的流媒体
  82 + MediaServerDTO mediaServer =
  83 + tkMediaServerService.getMediaServerForPlay(tenantId, sipDeviceDTO.getMediaServerId());
  84 + if (null == mediaServer) {
  85 + throw new TkDataValidationException(
  86 + ErrorMessage.NOT_FOUND_MEDIA_SERVER_FOR_PLAY.getMessage());
  87 + }
  88 + // 根据设备ID和通道ID,找到对应的流信息
  89 + Optional<StreamInfoDTO> result =
  90 + tkCacheStorageService.queryPlayStreamByChannel(sipDeviceDTO.getCameraCode(), channelId);
  91 + if (result.isPresent()) {
  92 + StreamInfoDTO streamInfoDTO = result.get();
  93 + String streamId = streamInfoDTO.getStream();
  94 + if (StringUtils.isNotEmpty(streamId)) {
  95 + String mediaServerId = streamInfoDTO.getMediaServerId();
  96 + Optional<MediaServerDTO> mediaServerDTO = tkMediaServerNodeService.getOne(mediaServerId);
  97 + if (mediaServerDTO.isPresent()) {
  98 + if (checkRTPServerExist(mediaServerDTO.get(), streamInfoDTO, tenantId)) {
  99 + log.debug(String.format("缓存的【流媒体播放信息】内容【%s】", streamInfoDTO));
  100 + return Futures.immediateFuture(new StreamContentDTO(streamInfoDTO));
  101 + }
  102 + }
  103 + }
  104 + }
  105 +
  106 + StreamInfoDTO streamInfoDTO =
  107 + deviceFirstPlay(
  108 + currentUser, deviceDTO.getTbDeviceId(), mediaServer, sipDeviceDTO, videoChanelDTO);
  109 + if (Optional.ofNullable(streamInfoDTO).isPresent()) {
  110 + return Futures.immediateFuture(new StreamContentDTO(streamInfoDTO));
  111 + } else {
  112 + return Futures.immediateFuture(new StreamContentDTO(null));
  113 + }
  114 + }
  115 +
  116 + @Override
  117 + public StreamInfoDTO onPublishHandlerForPlay(
  118 + MediaServerDTO mediaServerItem, JsonNode response, String deviceCode, String channelId) {
  119 + StreamInfoDTO streamInfo = onPublishHandler(mediaServerItem, response, deviceCode, channelId);
  120 + VideoChanelDTO videoChanel =
  121 + tkVideoChannelService.findVideoChannelById(
  122 + deviceCode, channelId, mediaServerItem.getTenantId());
  123 + if (videoChanel != null) {
  124 + videoChanel.setStreamId(streamInfo.getStream());
  125 + tkVideoChannelService.updateVideoChannelStreamId(
  126 + streamInfo.getStream(), deviceCode, channelId);
  127 + }
  128 + tkCacheStorageService.cacheStreamInfoByStartPlay(streamInfo);
  129 + return streamInfo;
  130 + }
  131 +
  132 + @Override
  133 + public boolean stopPlay(SecurityUser currentUser, String tbDeviceId, String channelId) throws ThingsboardException {
  134 + String tenantId = currentUser.getCurrentTenantId();
  135 + DeviceDTO deviceDTO = tkDeviceService.findDeviceInfoByTbDeviceId(tenantId, tbDeviceId);
  136 + if (null == deviceDTO
  137 + || !deviceDTO.getDeviceInfo().has(FastIotConstants.DeviceAdditional.SIP)) {
  138 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  139 + }
  140 + String cameraCode =
  141 + deviceDTO
  142 + .getDeviceInfo()
  143 + .get(FastIotConstants.DeviceAdditional.SIP)
  144 + .get(FastIotConstants.ZLMediaBody.CAMERA_CODE)
  145 + .asText();
  146 +
  147 + Optional<StreamInfoDTO> streamInfoDTO =
  148 + tkCacheStorageService.queryPlayStreamByChannel(cameraCode, channelId);
  149 + if (streamInfoDTO.isEmpty()) {
  150 + throw new TkDataValidationException(
  151 + ErrorMessage.STREAM_INFO_NOT_FOUND_FOR_PLAY.getMessage());
  152 + }
  153 + byeCmdInSsrcTransaction(
  154 + currentUser.getCurrentTenantId(),
  155 + false,
  156 + tbDeviceId,
  157 + cameraCode,channelId,streamInfoDTO.get().getStream(),
  158 + fromDeviceRpcResponse->{
  159 +
  160 + });
  161 +
  162 + tkCacheStorageService.deleteCacheStreamInfoByStopPlay(streamInfoDTO.get()); //redisCatchStorage.stopPlay(streamInfo);
  163 + return tkVideoChannelService.updateVideoChannelStreamId(null, cameraCode, channelId);//storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  164 + }
  165 +
  166 + @Override
  167 + public StreamInfoDTO play(
  168 + SecurityUser currentUser,
  169 + String tbDeviceId,
  170 + MediaServerDTO mediaServerDTO,
  171 + SsrcInfoDTO ssrcInfoDTO,
  172 + SipDeviceDTO sipDeviceDTO,
  173 + String channelId) {
  174 + HookSubscribeForStreamChange hookSubscribe =
  175 + new HookSubscribeForStreamChange(
  176 + "rtp", ssrcInfoDTO.getStream(), true, "rtsp", mediaServerDTO.getMediaServerId());
  177 + CountDownLatch timeoutLatch = new CountDownLatch(1);
  178 + // 定时任务超时KEY
  179 + String timeOutTaskKey = UUID.randomUUID().toString();
  180 + zlMediaKitTaskUtils.startCronTaskDelay(
  181 + timeOutTaskKey,
  182 + () -> {
  183 + log.debug(
  184 + "[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}",
  185 + sipDeviceDTO.getCameraCode(),
  186 + channelId,
  187 + ssrcInfoDTO.getPort(),
  188 + ssrcInfoDTO.getSsrc());
  189 + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  190 + try {
  191 + // cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  192 + // TODO 回复Bye命令
  193 + } catch (Exception e) {
  194 + log.error("[点播超时], 发送BYE失败 {}", e.getMessage());
  195 + } finally {
  196 + // timeoutCallback.run(1, "收流超时");
  197 + // 释放SSRC
  198 + tkMediaServerNodeService.releaseSsrc(
  199 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  200 + // streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  201 + // 关闭RTP服务
  202 + tkMediaServerNodeService.closeRTPServer(
  203 + Optional.of(mediaServerDTO), ssrcInfoDTO.getStream());
  204 + // 取消订阅消息监听
  205 + subscribe.removeSubscribe(hookSubscribe);
  206 + }
  207 + },
  208 + userSetting.getPlayTimeout());
  209 + // 获取端口信息失败,则不发送点播指令
  210 + if (ssrcInfoDTO.getPort() <= FastIotConstants.MagicNumber.ZERO) {
  211 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  212 + // 释放SSRC
  213 + tkMediaServerNodeService.releaseSsrc(
  214 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  215 + videoStreamSessionManager.remove(
  216 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  217 + throw new TkDataValidationException(ErrorMessage.GET_PLAY_PORT_FAILED.getMessage());
  218 + }
  219 + // 进行命令发送
  220 + AtomicReference<StreamInfoDTO> result = new AtomicReference<>();
  221 + try {
  222 +
  223 + log.debug(
  224 + String.format(
  225 + "视频流【%s】的流媒体服务器信息=流媒体服务ID【%s】+流媒体服务端口【%s】+SDP(会话)IP【%s】",
  226 + ssrcInfoDTO.getStream(),
  227 + mediaServerDTO.getMediaServerId(),
  228 + ssrcInfoDTO.getPort(),
  229 + mediaServerDTO.getSdpIp()));
  230 + ObjectNode paramJson = JacksonUtil.newObjectNode();
  231 + paramJson.set(FastIotConstants.ZLMediaBody.MEDIA, JacksonUtil.valueToTree(mediaServerDTO));
  232 + //
  233 + // paramJson.put(FastIotConstants.ZLMediaBody.MEDIA_ID,mediaServerDTO.getMediaServerId());
  234 + // paramJson.put(FastIotConstants.ZLMediaBody.MEDIA_SDP_IP,mediaServerDTO.getSdpIp());
  235 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_STREAM, ssrcInfoDTO.getStream());
  236 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_PORT, ssrcInfoDTO.getPort());
  237 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_SSRC, ssrcInfoDTO.getSsrc());
  238 + paramJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  239 + // TODO: 2023/12/28 收到流改变事件后,视频点播的业务逻辑。
  240 + subscribe.addSubscribe(
  241 + hookSubscribe,
  242 + (MediaServerDTO mediaServerItemInUse, JsonNode response) -> {
  243 + log.debug(
  244 + "处理订阅消息:主题【{}】流媒体信息【{}】收到的内容【{}】",
  245 + hookSubscribe.getHookType().name(),
  246 + mediaServerItemInUse,
  247 + response);
  248 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  249 +
  250 + subscribe.removeSubscribe(hookSubscribe);
  251 + result.set(
  252 + steamImage(
  253 + ssrcInfoDTO.getStream(),
  254 + sipDeviceDTO.getCameraCode(),
  255 + channelId,
  256 + mediaServerItemInUse,
  257 + response));
  258 + timeoutLatch.countDown();
  259 + });
  260 + cameraCommonCmd(
  261 + currentUser.getCurrentTenantId(),
  262 + paramJson,
  263 + sipDeviceDTO.getCameraCode(),
  264 + VideoMethodEnum.INVITE,
  265 + false,
  266 + tbDeviceId,
  267 + fromDeviceRpcResponse -> {
  268 + log.warn("【流媒体SIP】收到【视频点播】结果=异常【{}】+数据【{}】",fromDeviceRpcResponse.getError(), fromDeviceRpcResponse.getResponse());
  269 + fromDeviceRpcResponse
  270 + .getResponse()
  271 + .ifPresent(
  272 + jsonStr -> {
  273 + JsonNode responseJson = JacksonUtil.toJsonNode(jsonStr);
  274 + if (fromDeviceRpcResponse.getError().isEmpty()) {
  275 + playSuccess(
  276 + currentUser,
  277 + tbDeviceId,
  278 + mediaServerDTO,
  279 + responseJson,
  280 + ssrcInfoDTO,
  281 + sipDeviceDTO,
  282 + channelId,
  283 + timeOutTaskKey);
  284 + } else {
  285 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  286 + tkMediaServerNodeService.closeRTPServer(
  287 + mediaServerDTO, ssrcInfoDTO.getStream(), null);
  288 + // 释放ssrc
  289 + tkMediaServerNodeService.releaseSsrc(
  290 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  291 + videoStreamSessionManager.remove(
  292 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  293 + // errorEvent.response(event);
  294 + }
  295 + });
  296 + });
  297 + timeoutLatch.await(userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS);
  298 + } catch (Exception e) {
  299 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  300 + tkMediaServerNodeService.closeRTPServer(
  301 + Optional.ofNullable(mediaServerDTO), ssrcInfoDTO.getStream());
  302 + // 释放ssrc
  303 + tkMediaServerNodeService.releaseSsrc(mediaServerDTO.getId(), ssrcInfoDTO.getSsrc());
  304 +
  305 + videoStreamSessionManager.remove(
  306 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  307 +
  308 + // SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
  309 + // eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
  310 + // eventResult.statusCode = -1;
  311 + // eventResult.msg = "命令发送失败";
  312 + // errorEvent.response(eventResult);
  313 + }
  314 + log.debug(String.format("最新的【流媒体播放信息】内容【%s】", result.get()));
  315 + return result.get();
  316 + }
  317 +
  318 + /**
  319 + * 调用流媒体接口获取视频流封面图,并触发后续业务
  320 + *
  321 + * @param ssrcStream
  322 + * @param cameraCode
  323 + * @param channelId
  324 + * @param mediaServerItemInUse
  325 + * @param response
  326 + */
  327 + private StreamInfoDTO steamImage(
  328 + String ssrcStream,
  329 + String cameraCode,
  330 + String channelId,
  331 + MediaServerDTO mediaServerItemInUse,
  332 + JsonNode response) {
  333 + // hook响应
  334 + StreamInfoDTO streamInfoDTO =
  335 + onPublishHandlerForPlay(mediaServerItemInUse, response, cameraCode, channelId);
  336 + String streamUrl;
  337 + if (mediaServerItemInUse.getRtspPort() != 0) {
  338 + streamUrl =
  339 + String.format(
  340 + "rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInUse.getRtspPort(), "rtp", ssrcStream);
  341 + } else {
  342 + streamUrl =
  343 + String.format(
  344 + "http://127.0.0.1:%s/%s/%s.live.mp4",
  345 + mediaServerItemInUse.getHttpPort(), "rtp", ssrcStream);
  346 + }
  347 + String path = "snap";
  348 + String fileName = cameraCode + "_" + channelId + ".jpg";
  349 + // 请求截图
  350 + log.debug("[请求截图]: " + fileName);
  351 + zlMediaKitRestFulUtils.getSnap(mediaServerItemInUse, streamUrl, 15, 1, path, fileName);
  352 + return streamInfoDTO;
  353 + }
  354 +
  355 + /**
  356 + * 构建视频点播的流媒体信息
  357 + *
  358 + * @param mediaServerItem
  359 + * @param response
  360 + * @param deviceCode
  361 + * @param channelId
  362 + * @return
  363 + */
  364 + private StreamInfoDTO onPublishHandler(
  365 + MediaServerDTO mediaServerItem, JsonNode response, String deviceCode, String channelId) {
  366 + String streamId = response.get("stream").asText();
  367 + JsonNode tracks = response.get("tracks");
  368 + StreamInfoDTO streamInfo =
  369 + tkMediaServerService.getStreamInfoByAppAndStream(
  370 + mediaServerItem, "rtp", streamId, tracks, null);
  371 + streamInfo.setCameraCode(deviceCode);
  372 + streamInfo.setChannelId(channelId);
  373 + return streamInfo;
  374 + }
  375 +
  376 + private StreamInfoDTO deviceFirstPlay(
  377 + SecurityUser currentUser,
  378 + String tbDeviceId,
  379 + MediaServerDTO mediaServerDTO,
  380 + SipDeviceDTO sipDeviceDTO,
  381 + VideoChanelDTO videoChanelDTO) {
  382 + // 进行点播时,需要的参数包含:schema、vhost、app、stream
  383 + String streamId = null;
  384 + // 是否开启了多端口模式
  385 + if (mediaServerDTO.isRtpEnable()) {
  386 + streamId =
  387 + String.format("%s_%s", sipDeviceDTO.getCameraCode(), videoChanelDTO.getChannelId());
  388 + }
  389 + // 开启收流服务
  390 + SsrcInfoDTO ssrcInfoDTO =
  391 + tkMediaServerNodeService.openRTPServer(
  392 + mediaServerDTO,
  393 + streamId,
  394 + null,
  395 + sipDeviceDTO.isSsrcCheck(),
  396 + false,
  397 + 0,
  398 + false,
  399 + sipDeviceDTO.getStreamModeForParam());
  400 + if (null == ssrcInfoDTO) {
  401 + throw new TkDataValidationException(ErrorMessage.RECEIVE_STREAM_FAILED.getMessage());
  402 + }
  403 + // 开始进行点播
  404 + return play(
  405 + currentUser,
  406 + tbDeviceId,
  407 + mediaServerDTO,
  408 + ssrcInfoDTO,
  409 + sipDeviceDTO,
  410 + videoChanelDTO.getChannelId());
  411 + }
  412 +
  413 + private boolean checkRTPServerExist(
  414 + MediaServerDTO mediaServerDTO, StreamInfoDTO streamInfo, String tenantId) {
  415 + String streamId = streamInfo.getStream();
  416 + JsonNode rtpInfo = zlMediaKitRestFulUtils.getRtpInfo(mediaServerDTO, streamId);
  417 + if (rtpInfo.get("code").asInt() == 0 && rtpInfo.get("exist").asBoolean()) {
  418 + int localPort = rtpInfo.get("local_port").asInt();
  419 + if (localPort == 0) {
  420 + throw new TkDataValidationException("点播已经在进行中,请稍候重试");
  421 + }
  422 + return true;
  423 + } else {
  424 + String deviceId = streamInfo.getCameraCode();
  425 + String channelId = streamInfo.getChannelId();
  426 + tkCacheStorageService.deleteCacheStreamInfoByStopPlay(streamInfo);
  427 + tkVideoChannelService.updateVideoChannelStreamId(null, deviceId, channelId);
  428 + return false;
  429 + }
  430 + }
  431 +
  432 + @Value("${server.rest.server_side_rpc.min_timeout:5000}")
  433 + protected long minTimeout;
  434 +
  435 + @Value("${server.rest.server_side_rpc.default_timeout:10000}")
  436 + protected long defaultTimeout;
  437 +
  438 + /**
  439 + * 公用的命令下发接口
  440 + *
  441 + * @param tenantId
  442 + * @param paramJson
  443 + * @param oneWay
  444 + * @param deviceId TB的设备ID
  445 + * @param responseConsumer
  446 + * @throws ThingsboardException
  447 + */
  448 + public void cameraCommonCmd(
  449 + String tenantId,
  450 + ObjectNode paramJson,
  451 + String cameraCode,
  452 + VideoMethodEnum method,
  453 + boolean oneWay,
  454 + String deviceId,
  455 + Consumer<FromDeviceRpcResponse> responseConsumer)
  456 + throws ThingsboardException {
  457 + try {
  458 +
  459 + paramJson.put(FastIotConstants.ZLMediaBody.CAMERA_CODE, cameraCode);
  460 + paramJson.put(FastIotConstants.ZLMediaBody.METHOD_TYPE, method.name());
  461 + ToDeviceRpcRequestBody body =
  462 + new ToDeviceRpcRequestBody("cameraMethod", JacksonUtil.toString(paramJson));
  463 + long timeout = defaultTimeout;
  464 + long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
  465 + UUID rpcRequestUUID = UUID.randomUUID();
  466 + boolean persisted = false;
  467 + ToDeviceRpcRequest rpcRequest =
  468 + new ToDeviceRpcRequest(
  469 + rpcRequestUUID, new TenantId(UUID.fromString(tenantId)),
  470 + new DeviceId(UUID.fromString(deviceId)), oneWay, expTime, body, persisted, null, null);
  471 + deviceRpcService.processRestApiRpcRequest(rpcRequest, responseConsumer, null);
  472 + } catch (IllegalArgumentException ioe) {
  473 + throw new ThingsboardException(
  474 + "Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  475 + }
  476 + }
  477 +
  478 + /**
  479 + * 点播关闭命令下发
  480 + *
  481 + * @param oneWay
  482 + * @param tbDeviceId TB的设备ID
  483 + * @param responseConsumer
  484 + * @throws ThingsboardException
  485 + */
  486 + @Override
  487 + public void byeCmdInSsrcTransaction(
  488 + String tenantId,
  489 + boolean oneWay,
  490 + String tbDeviceId,
  491 + String cameraCode,String channelId,String streamId,
  492 + Consumer<FromDeviceRpcResponse> responseConsumer)
  493 + throws ThingsboardException {
  494 + Optional<SsrcTransactionDTO> transactionDTO = videoStreamSessionManager.getSsrcTransaction(cameraCode,channelId,streamId);
  495 + if (transactionDTO.isEmpty()) {
  496 + throw new TkDataValidationException(
  497 + ErrorMessage.STREAM_INFO_NOT_FOUND_FOR_PLAY.getMessage());
  498 + }
  499 + SsrcTransactionDTO ssrc = transactionDTO.get();
  500 + cameraByeCmd(
  501 + tenantId,ssrc.getSipTransactionInfo(),oneWay,true,tbDeviceId,
  502 + cameraCode,channelId,streamId,ssrc.getSsrc(),ssrc.getMediaServerId(),
  503 + responseConsumer);
  504 + }
  505 +
  506 + public void byeCmdInSendRtp(
  507 + SecurityUser currentUser,
  508 + boolean oneWay,
  509 + String tbDeviceId,
  510 + String cameraCode,String channelId,String streamId,
  511 + Consumer<FromDeviceRpcResponse> responseConsumer)
  512 + throws ThingsboardException {
  513 +
  514 +// SendRtpItemDTO sendRtpItem =null;
  515 +// SipMessageHeaderDTO sipTransactionInfo =
  516 +// SipMessageHeaderDTO.builder()
  517 +// .toTag(sendRtpItem.getToTag())
  518 +// .fromTag(sendRtpItem.getFromTag())
  519 +// .callId(sendRtpItem.getCallId())
  520 +// .build();
  521 +// MediaServerDTO mediaServer = tkMediaServerService.getMediaServerByMediaServerId(sendRtpItem.getMediaServerId());
  522 +// cameraByeCmd(
  523 +// currentUser,ssrc.getSipTransactionInfo(),oneWay,mediaServer != null,tbDeviceId,
  524 +// cameraCode,sendRtpItem.getChannelId(),streamId,ssrc.getSsrc(),ssrc.getMediaServerId(),
  525 +// responseConsumer);
  526 + }
  527 +
  528 +
  529 + public void cameraByeCmd(
  530 + String currentUser,SipMessageHeaderDTO messageHeaderDTO,
  531 + boolean oneWay,boolean mediaOnline,
  532 + String tbDeviceId,
  533 + String cameraCode,String channelId,String streamId,String ssrc,String mediaServerId,
  534 + Consumer<FromDeviceRpcResponse> responseConsumer) throws ThingsboardException {
  535 + if(mediaOnline){
  536 + tkMediaServerNodeService.releaseSsrc(mediaServerId,ssrc);
  537 + tkMediaServerNodeService.closeRTPServer(mediaServerId,streamId);
  538 + }
  539 + videoStreamSessionManager.remove(cameraCode,channelId,streamId);
  540 + ObjectNode paramJson = JacksonUtil.newObjectNode();
  541 + paramJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  542 + paramJson.set(FastIotConstants.ZLMediaBody.MSG_HEADER, JacksonUtil.valueToTree(messageHeaderDTO));
  543 + cameraCommonCmd(
  544 + currentUser,
  545 + paramJson,
  546 + cameraCode,
  547 + VideoMethodEnum.BYE,
  548 + oneWay,
  549 + tbDeviceId,
  550 + responseConsumer);
  551 + }
  552 +
  553 + /**
  554 + * 点播成功后,流媒体的业务逻辑
  555 + *
  556 + * @param currentUser
  557 + * @param tbDeviceId
  558 + * @param mediaServerDTO
  559 + * @param responseJson
  560 + * @param ssrcInfo
  561 + * @param sipDeviceDTO
  562 + * @param channelId
  563 + * @param timeOutTaskKey
  564 + */
  565 + private void playSuccess(
  566 + SecurityUser currentUser,
  567 + String tbDeviceId,
  568 + MediaServerDTO mediaServerDTO,
  569 + JsonNode responseJson,
  570 + SsrcInfoDTO ssrcInfo,
  571 + SipDeviceDTO sipDeviceDTO,
  572 + String channelId,
  573 + String timeOutTaskKey) {
  574 + String cameraCode = sipDeviceDTO.getCameraCode();
  575 + String callId = responseJson.get(FastIotConstants.ZLMediaBody.CALL_ID).asText();
  576 + JsonNode msgHeader = responseJson.get(FastIotConstants.ZLMediaBody.MSG_HEADER);
  577 + SipMessageHeaderDTO sipMessage = JacksonUtil.convertValue(msgHeader, SipMessageHeaderDTO.class);
  578 + String sessionType = responseJson.get(FastIotConstants.ZLMediaBody.SESSION_TYPE).asText();
  579 + String streamId = ssrcInfo.getStream();
  580 + videoStreamSessionManager.put(
  581 + cameraCode,
  582 + channelId,
  583 + callId,
  584 + streamId,
  585 + ssrcInfo.getSsrc(),
  586 + sipMessage,
  587 + mediaServerDTO.getMediaServerId(),
  588 + SessionTypeEnum.valueOf(sessionType));
  589 + // 获取ssrc
  590 + String contentString = responseJson.get(FastIotConstants.ZLMediaBody.MSG_CONTEXT).asText();
  591 + int ssrcIndex = contentString.indexOf("y=");
  592 + // 检查是否有y字段
  593 + if (ssrcIndex >= 0) {
  594 + // ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  595 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
  596 + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  597 + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  598 + if (sipDeviceDTO.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  599 + String substring = contentString.substring(0, contentString.indexOf("y="));
  600 + try {
  601 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  602 + int port = -1;
  603 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  604 + for (Object description : mediaDescriptions) {
  605 + MediaDescription mediaDescription = (MediaDescription) description;
  606 + Media media = mediaDescription.getMedia();
  607 +
  608 + Vector mediaFormats = media.getMediaFormats(false);
  609 + if (mediaFormats.contains("96")) {
  610 + port = media.getMediaPort();
  611 + break;
  612 + }
  613 + }
  614 + log.debug(
  615 + "[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  616 + sipDeviceDTO.getCameraCode(),
  617 + channelId,
  618 + sdp.getConnection().getAddress(),
  619 + port,
  620 + sipDeviceDTO.getStreamMode(),
  621 + ssrcInfo.getSsrc(),
  622 + sipDeviceDTO.isSsrcCheck());
  623 + JsonNode jsonNode =
  624 + zlMediaKitRestFulUtils.connectRtpServer(
  625 + mediaServerDTO, sdp.getConnection().getAddress(), port, streamId);
  626 + log.debug("[点播-TCP主动连接对方] 结果: {}", jsonNode);
  627 + } catch (SdpException e) {
  628 + log.error(
  629 + "[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败",
  630 + sipDeviceDTO.getCameraCode(),
  631 + channelId,
  632 + e);
  633 + }
  634 + }
  635 + return;
  636 + }
  637 + log.debug("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  638 + if (!mediaServerDTO.isRtpEnable() || sipDeviceDTO.isSsrcCheck()) {
  639 + log.debug("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  640 +
  641 + // 释放不被使用的ssrc
  642 + tkMediaServerNodeService.releaseSsrc(mediaServerDTO.getMediaServerId(), ssrcInfo.getSsrc());
  643 + // 单端口模式streamId也有变化,需要重新设置监听
  644 + if (!mediaServerDTO.isRtpEnable()) {
  645 + // 添加订阅
  646 + HookSubscribeForStreamChange hookSubscribe =
  647 + new HookSubscribeForStreamChange(
  648 + "rtp", streamId, true, "rtsp", mediaServerDTO.getMediaServerId());
  649 + subscribe.removeSubscribe(hookSubscribe);
  650 + hookSubscribe
  651 + .getContent()
  652 + .put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  653 + subscribe.addSubscribe(
  654 + hookSubscribe,
  655 + (MediaServerDTO mediaServerItemInUse, JsonNode response) -> {
  656 + log.debug(
  657 + "处理订阅消息:主题【{}】流媒体信息【{}】收到的内容【{}】",
  658 + hookSubscribe.getHookType().name(),
  659 + mediaServerItemInUse,
  660 + response);
  661 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  662 + // hook响应
  663 + onPublishHandlerForPlay(mediaServerItemInUse, response, cameraCode, channelId);
  664 + // hookEvent.response(mediaServerItemInUse, response);
  665 + });
  666 + }
  667 +
  668 + // 关闭rtp server
  669 + tkMediaServerNodeService.closeRTPServer(
  670 + mediaServerDTO,
  671 + streamId,
  672 + result -> {
  673 + if (result) {
  674 + // 重新开启ssrc server
  675 + tkMediaServerNodeService.openRTPServer(
  676 + mediaServerDTO,
  677 + streamId,
  678 + ssrcInResponse,
  679 + sipDeviceDTO.isSsrcCheck(),
  680 + false,
  681 + ssrcInfo.getPort(),
  682 + true,
  683 + sipDeviceDTO.getStreamModeForParam());
  684 + } else {
  685 + try {
  686 + log.warn("[停止点播] {}/{}", cameraCode, channelId);
  687 + byeCmdInSsrcTransaction(
  688 + currentUser.getCurrentTenantId(),
  689 + false,
  690 + tbDeviceId,
  691 + cameraCode,channelId,streamId,
  692 + fromDeviceRpcResponse->{
  693 +
  694 + });
  695 + } catch (Exception e) {
  696 + log.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
  697 + }
  698 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  699 + videoStreamSessionManager.remove(cameraCode, channelId, streamId);
  700 + // event.msg = "下级自定义了ssrc,重新设置收流信息失败";
  701 + // event.statusCode = 500;
  702 + // errorEvent.response(event);
  703 + }
  704 + });
  705 + }
  706 + }
  707 + }
  708 +
  709 + @Override
  710 + public boolean control(SecurityUser currentUser, String tbDeviceId, String channelId, PTZCommandEnum command, int horizonSpeed, int verticalSpeed, int zoomSpeed) throws ThingsboardException {
  711 +
  712 + if (PTZCommandEnum.STOP.equals(command)) {
  713 + horizonSpeed = 0;
  714 + verticalSpeed = 0;
  715 + zoomSpeed = 0;
  716 + }
  717 + DeviceDTO deviceDTO = tkDeviceService.checkDeviceByTenantIdAndId(currentUser.getCurrentTenantId(), tbDeviceId, true);
  718 + if (null == deviceDTO) {
  719 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  720 + }
  721 + SipDeviceDTO sipDeviceDTO =
  722 + JacksonUtil.convertValue(
  723 + deviceDTO.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP),
  724 + SipDeviceDTO.class);
  725 + if (null == sipDeviceDTO) {
  726 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  727 + }
  728 + PTZCmdDTO ptzCmdDTO = new PTZCmdDTO();
  729 + ptzCmdDTO.setCommand(command);
  730 + ptzCmdDTO.setHorizonSpeed(horizonSpeed);
  731 + ptzCmdDTO.setVerticalSpeed(verticalSpeed);
  732 + ptzCmdDTO.setZoomSpeed(zoomSpeed);
  733 + ObjectNode requestJson = JacksonUtil.newObjectNode();
  734 + requestJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  735 + requestJson.set(FastIotConstants.ZLMediaBody.CONTROL_CONTEXT, JacksonUtil.valueToTree(ptzCmdDTO));
  736 + cameraCommonCmd(
  737 + currentUser.getCurrentTenantId(),
  738 + requestJson,
  739 + sipDeviceDTO.getCameraCode(),
  740 + VideoMethodEnum.MESSAGE,
  741 + false,
  742 + tbDeviceId,
  743 + fromDeviceRpcResponse->{
  744 +
  745 + });
  746 + return false;
  747 + }
  748 +}
... ...
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import lombok.Getter;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.beans.factory.annotation.Value;
  7 +import org.springframework.stereotype.Component;
  8 +import org.thingsboard.server.common.data.id.EntityId;
  9 +import org.thingsboard.server.common.data.yunteng.config.media.ZLMediaKitServerConfig;
  10 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  11 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  12 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  13 +import org.thingsboard.server.common.data.yunteng.utils.ZLMediaKitRestFulUtils;
  14 +import org.thingsboard.server.dao.util.yunteng.ZLMediaKitTaskUtils;
  15 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerNodeService;
  16 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerService;
  17 +import org.thingsboard.server.common.data.yunteng.config.media.MediaConfig;
  18 +import org.thingsboard.server.queue.util.TbCoreComponent;
  19 +
  20 +import javax.annotation.PostConstruct;
  21 +import java.util.List;
  22 +import java.util.Map;
  23 +import java.util.Optional;
  24 +
  25 +@Component("ZLMediaKitState")
  26 +@TbCoreComponent
  27 +@Slf4j
  28 +public class ZLMediaKitStateRunner {
  29 + private final ZLMediaKitRestFulUtils zlMediaKitRestFulUtils;
  30 + private final TkMediaServerNodeService tkMediaServerNodeService;
  31 + private final TkMediaServerService tkMediaServerService;
  32 + private final MediaConfig mediaConfig;
  33 + private final ZLMediaKitTaskUtils zlMediaKitTaskUtils;
  34 + private final Map<String, Boolean> startGetMedia;
  35 +
  36 + @Value("${media.defaultStateCheckIntervalInSec}")
  37 + @Getter
  38 + private int defaultStateCheckIntervalInSec;
  39 +
  40 + public ZLMediaKitStateRunner(
  41 + ZLMediaKitRestFulUtils zlMediaKitRestFulUtils,
  42 + TkMediaServerNodeService tkMediaServerNodeService,
  43 + TkMediaServerService tkMediaServerService,
  44 + MediaConfig mediaConfig,
  45 + ZLMediaKitTaskUtils zlMediaKitTaskUtils,
  46 + Map<String, Boolean> startGetMedia) {
  47 + this.zlMediaKitRestFulUtils = zlMediaKitRestFulUtils;
  48 + this.tkMediaServerNodeService = tkMediaServerNodeService;
  49 + this.tkMediaServerService = tkMediaServerService;
  50 + this.mediaConfig = mediaConfig;
  51 + this.zlMediaKitTaskUtils = zlMediaKitTaskUtils;
  52 + this.startGetMedia = startGetMedia;
  53 + }
  54 +
  55 + @PostConstruct
  56 + public void init() {
  57 + updateDefaultMediaServer();
  58 + // 从数据库获取所有的流媒体信息
  59 + List<MediaServerDTO> dtoList = tkMediaServerService.getAllMediaKit();
  60 +
  61 + Optional.ofNullable(dtoList).ifPresent( all ->{
  62 + all.forEach(dto ->{
  63 + String key = dto.getMediaServerId();
  64 + startGetMedia.put(key, true);
  65 + zlMediaKitTaskUtils.startCronTask(
  66 + key,
  67 + () -> {
  68 + ZLMediaKitServerConfig zlMediaKitServerConfig = getMediaServerConfig(dto);
  69 + if (null != zlMediaKitServerConfig) {
  70 + startGetMedia.remove(dto.getMediaServerId());
  71 + zlMediaKitTaskUtils.stop(key);
  72 + zlMediaKitServerConfig.setIp(dto.getIp());
  73 + zlMediaKitServerConfig.setHttpPort(dto.getHttpPort());
  74 + zlMediaKitServerConfig.setTenantId(dto.getTenantId());
  75 + // 进行上线操作
  76 + tkMediaServerNodeService.zlmServerOnline(zlMediaKitServerConfig);
  77 + }
  78 + },
  79 + defaultStateCheckIntervalInSec);
  80 + });
  81 + });
  82 + }
  83 +
  84 + private void updateDefaultMediaServer() {
  85 + // 默认流媒体服务器
  86 + MediaServerDTO oldMediaServer = tkMediaServerService.findDefaultMediaServer();
  87 + MediaServerDTO defaultConfig = mediaConfig.getMediaSerItem();
  88 + if (oldMediaServer == null) {
  89 + defaultConfig.setTenantId(EntityId.NULL_UUID.toString());
  90 + }else{
  91 + defaultConfig.setId(oldMediaServer.getId());
  92 + defaultConfig.setTenantId(oldMediaServer.getTenantId());
  93 + }
  94 + tkMediaServerService.saveOrUpdateMediaServer(defaultConfig);
  95 + }
  96 +
  97 + /**
  98 + * 调用流媒体API接口获取流媒体配置
  99 + * @param mediaServerItem
  100 + * @return
  101 + */
  102 + private ZLMediaKitServerConfig getMediaServerConfig(MediaServerDTO mediaServerItem) {
  103 + log.error("启动流媒体【ZLMedia】验证,流媒体编号【{}】",mediaServerItem.getMediaServerId());
  104 + if (startGetMedia == null) {
  105 + return null;
  106 + }
  107 + if (!mediaServerItem.isDefaultServer()
  108 + && tkMediaServerNodeService.getOne(mediaServerItem.getMediaServerId()).isEmpty()) {
  109 + return null;
  110 + }
  111 + if (startGetMedia.get(mediaServerItem.getMediaServerId()) == null
  112 + || !startGetMedia.get(mediaServerItem.getMediaServerId())) {
  113 + return null;
  114 + }
  115 + JsonNode responseJson = zlMediaKitRestFulUtils.getMediaServerConfig(mediaServerItem);
  116 + if(responseJson == null){
  117 + log.error("流媒体【{}】服务地址【{}:{}】无法访问",mediaServerItem.getMediaServerId(),mediaServerItem.getIp(),mediaServerItem.getHttpPort());
  118 + return null;
  119 + }
  120 + JsonNode data = responseJson.get(FastIotConstants.ZLMediaBody.DATA);
  121 + if (data != null && !data.isEmpty()) {
  122 + log.error("流媒体【{}:{}】调用成功,响应内容【{}】",mediaServerItem.getIp(),mediaServerItem.getHttpPort(),data);
  123 + return JacksonUtil.convertValue(data.get(0), ZLMediaKitServerConfig.class);
  124 + }
  125 + log.error("流媒体【{}:{}】调用失败,失败原因【{}】",mediaServerItem.getIp(),mediaServerItem.getHttpPort(),responseJson.get(FastIotConstants.ZLMediaBody.MSG));
  126 + return null;
  127 + }
  128 +}
... ...
... ... @@ -451,6 +451,12 @@ caffeine:
451 451 sceneReact:
452 452 timeToLiveInMinutes: "${CACHE_SPECS_SCENE_TTL:1440}"
453 453 maxSize: "${CACHE_SPECS_SCENE_MAX_SIZE:10000}"
  454 + tkSipCacheName:
  455 + timeToLiveInMinutes: "${CACHE_SPECS_TK_SIP_TTL:1440}"
  456 + maxSize: "${CACHE_SPECS_TK_SIP_MAX_SIZE:10000}"
  457 + tkMediaServerCacheName:
  458 + timeToLiveInMinutes: "${CACHE_SPECS_TK_MEDIA_SERVER_TTL:1440}"
  459 + maxSize: "${CACHE_SPECS_TK_MEDIA_SERVER_MAX_SIZE:10000}"
454 460 redis:
455 461 # standalone or cluster
456 462 connection:
... ... @@ -476,7 +482,7 @@ redis:
476 482 # db index
477 483 db: "${REDIS_DB:0}"
478 484 # db password
479   - password: "${REDIS_PASSWORD:}"
  485 + password: "${REDIS_PASSWORD:redis@6379}"
480 486 # pool config
481 487 pool_config:
482 488 maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}"
... ... @@ -712,6 +718,9 @@ transport:
712 718 # Skip certificate validity check for client certificates.
713 719 skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
714 720 # Local CoAP transport parameters
  721 + gbt28181:
  722 + # Enable/disable gbt28181 transport protocol.
  723 + enabled: "${GBT28181_ENABLED:true}"
715 724 tcp:
716 725 # Enable/disable tcp transport protocol.
717 726 enabled: "${TCP_ENABLED:true}"
... ... @@ -1194,10 +1203,10 @@ file:
1194 1203 type: ${FILE_STORAGE_TYPE:minio} #minio, or other to be implemented
1195 1204 randomFileName: ${FILE_STORAGE_FILENAME:true} #是否重命名文件名字,防止冲突
1196 1205 minio:
1197   - minioUrl: ${MINIO_URL:http://127.0.0.1:9000} #minio储存地址
1198   - minioName: ${MINIO_NAME:xxxxxx} #minio账户
1199   - minioPass: ${MINIO_PWD:xxxxx} #minio访问密码
1200   - bucketName: ${MINIO_BUCKET_NAME:yunteng-test} #minio储存桶名称
  1206 + minioUrl: ${MINIO_URL:https://demo.thingskit.com:9000} #minio储存地址
  1207 + minioName: ${MINIO_NAME:thingskit} #minio账户
  1208 + minioPass: ${MINIO_PWD:Dzr227+bjsz} #minio访问密码
  1209 + bucketName: yunteng-test #minio储存桶名称
1201 1210 randomFileName: ${file.storage.randomFileName}
1202 1211 account:
1203 1212 info:
... ... @@ -1218,3 +1227,45 @@ logging:
1218 1227 frp:
1219 1228 server:
1220 1229 address: ${FRP_SERVER_ADDRESS:http://127.0.0.1}
  1230 +sip:
  1231 + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡,
  1232 + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4
  1233 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的
  1234 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。
  1235 + ip: ${GBT28181_SIP_IP:192.168.1.15}
  1236 + # [可选] 28181服务监听的端口
  1237 + port: ${GBT28181_SIP_PORT:5060}
  1238 + #[可选]
  1239 + id: ${GBT28181_SIP_ID:44010200492000000001}
  1240 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
  1241 + # 后两位为行业编码,定义参照附录D.3
  1242 + # 3701020049标识山东济南历下区 信息行业接入
  1243 + # [可选]
  1244 + domain: ${GBT28181_SIP_DOMAIN:4401020049}
  1245 + #[可选]
  1246 + password: ${GBT28181_SIP_PASSWORD:61332286}
  1247 +#zlm 默认服务器配置
  1248 +media:
  1249 + id: ${GBT28181_MEDIA_GENERAL_ID:f6GfbO0BGEaROKLP}
  1250 + # [必须修改] zlm服务器的内网IP
  1251 + ip: ${GBT28181_MEDIA_IP:192.168.1.16}
  1252 + # [必须修改] zlm服务器的http.port
  1253 + http-port: ${GBT28181_MEDIA_HTTP_PORT:28080}
  1254 + # [可选] zlm服务器的hook.admin_params=secret
  1255 + secret: ${GBT28181_MEDIA_API_SECRET:5PnbPDCcxQGeK15OowHPPdSgort2Cx9Y}
  1256 + # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
  1257 + rtp:
  1258 + # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
  1259 + enable: true
  1260 + # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
  1261 + port-range: ${GBT28181_MEDIA_RTP_PORT_RANGE:30000,30500} # 端口范围
  1262 + # [可选] 国标级联在此范围内选择端口发送媒体流,
  1263 + send-port-range: ${GBT28181_MEDIA_RTP_PORT_RANGE:30000,30500} # 端口范围
  1264 + # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
  1265 + record-assist-port: 0
  1266 + defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}"
  1267 +
  1268 +thingskit:
  1269 + release:
  1270 + version: v1.1.1 Release
  1271 + date: 20230630
... ...
... ... @@ -8,7 +8,7 @@
8 8 * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
  11 + * distributed under the License is distributed on an "AS IS" BASIS,in
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
... ... @@ -130,10 +130,7 @@ message SessionEventMsg {
130 130 SessionEvent event = 2;
131 131 }
132 132
133   -//thingskit
134   -message PostEventMsg {
135   - repeated KeyValueProto kv = 1;
136   -}
  133 +
137 134 message PostTelemetryMsg {
138 135 repeated TsKvListProto tsKvList = 1;
139 136 }
... ... @@ -682,6 +679,7 @@ message TransportApiRequestMsg {
682 679
683 680 //Thingskit function
684 681 ScriptProto script = 14;
  682 + Gbt28181RequestMsg gbt28181RequestMsg = 15;
685 683 }
686 684
687 685 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -699,6 +697,7 @@ message TransportApiResponseMsg {
699 697
700 698 //Thingskit function
701 699 repeated ScriptProto scriptsResponseMsg = 11;
  700 + Gbt28181ResponseMsg gbt28181ResponseMsg = 12;
702 701 }
703 702
704 703 /* Messages that are handled by ThingsBoard Core Service */
... ... @@ -795,3 +794,37 @@ message ScriptProto{
795 794 string convertJs = 6;
796 795 int32 status = 7;
797 796 }
  797 +//thingskit function 设备上报的物模型事件
  798 +message PostEventMsg {
  799 + repeated KeyValueProto kv = 1;
  800 +}
  801 +message Gbt28181RequestMsg{
  802 + bytes context = 1;
  803 + string contextType = 2;
  804 + string callId = 3;
  805 + string fromTag = 4;
  806 + string toTag = 5;
  807 + string viaTag = 6;
  808 + string viaBranch = 7;
  809 + int32 sn = 8;
  810 + int64 tenantIdMSB = 9;
  811 + int64 tenantIdLSB = 10;
  812 + int64 entityIdMSB = 11;
  813 + int64 entityIdLSB = 12;
  814 +}
  815 +message Gbt28181ResponseMsg{
  816 + bytes context = 1;
  817 + string contextType = 2;
  818 + string callId = 3;
  819 + string fromTag = 4;
  820 + string toTag = 5;
  821 + string viaTag = 6;
  822 + string viaBranch = 7;
  823 + int32 sn = 8;
  824 + int64 tenantIdMSB = 9;
  825 + int64 tenantIdLSB = 10;
  826 + int64 entityIdMSB = 11;
  827 + int64 entityIdLSB = 12;
  828 +}
  829 +
  830 +
... ...
... ... @@ -48,6 +48,8 @@ public class DataConstants {
48 48 public static final String HTTP_TRANSPORT_NAME = "HTTP";
49 49 public static final String SNMP_TRANSPORT_NAME = "SNMP";
50 50
  51 + public static final String GBT_28181 = "GBT28181";
  52 +
51 53
52 54 public static final String[] allScopes() {
53 55 return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};
... ...
... ... @@ -21,5 +21,6 @@ public enum DeviceTransportType {
21 21 TCP,
22 22 COAP,
23 23 LWM2M,
24   - SNMP
  24 + SNMP,
  25 + GBT28181
25 26 }
... ...
... ... @@ -34,6 +34,7 @@ import java.io.Serializable;
34 34 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
35 35 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
36 36 @JsonSubTypes.Type(value = TkTcpDeviceTransportConfiguration.class, name = "TCP"),
  37 + @JsonSubTypes.Type(value = TkGBTDeviceTransportConfiguration.class, name = "GBT28181"),
37 38 @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
38 39 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"),
39 40 @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")})
... ...
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.data;
  17 +import lombok.Data;
  18 +import org.thingsboard.server.common.data.DeviceTransportType;
  19 +
  20 +@Data
  21 +public class TkGBTDeviceTransportConfiguration implements DeviceTransportConfiguration {
  22 + @Override
  23 + public DeviceTransportType getType() {
  24 + return DeviceTransportType.GBT28181;
  25 + }
  26 +
  27 +}
... ...
... ... @@ -32,6 +32,7 @@ import java.io.Serializable;
32 32 @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
33 33 @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
34 34 @JsonSubTypes.Type(value = TkTcpDeviceProfileTransportConfiguration.class, name = "TCP"),
  35 + @JsonSubTypes.Type(value = TkGBT28181DeviceProfileTransportConfiguration.class, name = "GBT28181"),
35 36 @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
36 37 @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"),
37 38 @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")
... ...
  1 +package org.thingsboard.server.common.data.device.profile;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.DeviceTransportType;
  5 +
  6 +@Data
  7 +public class TkGBT28181DeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
  8 + @Override
  9 + public DeviceTransportType getType() {
  10 + return DeviceTransportType.GBT28181;
  11 + }
  12 +
  13 +
  14 +}
... ...
1 1 package org.thingsboard.server.common.data.yunteng.common;
2 2
3 3 import org.thingsboard.server.common.data.yunteng.dto.SysDictItemDTO;
  4 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
4 5
5 6 public interface TkCommonService {
6 7 /**
... ... @@ -11,6 +12,4 @@ public interface TkCommonService {
11 12 * @return 返回字典Item表
12 13 */
13 14 SysDictItemDTO getDictValueByCodeAndText(String dictCode, String codeValue);
14   -
15   -
16 15 }
... ...
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +public interface CommonCallback<T>{
  4 + void run(T t);
  5 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +import java.util.*;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  7 +import org.springframework.stereotype.Component;
  8 +import org.thingsboard.server.common.data.yunteng.config.media.UserSetting;
  9 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  10 +import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils;
  11 +import org.thingsboard.server.common.data.yunteng.dto.sip.SipMessageHeaderDTO;
  12 +import org.thingsboard.server.common.data.yunteng.dto.sip.SsrcTransactionDTO;
  13 +import org.thingsboard.server.common.data.yunteng.enums.SessionTypeEnum;
  14 +
  15 +/** 视频流session管理器,管理视频预览、预览回放的通信句柄 */
  16 +@Component
  17 +@Slf4j
  18 +@ConditionalOnExpression(
  19 + "'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.gbt28181.enabled}'=='true')")
  20 +public class VideoStreamSessionManager {
  21 +
  22 + @Autowired private UserSetting userSetting;
  23 + @Autowired private CacheUtils cacheUtils;
  24 + private final String cacheName = FastIotConstants.MediaServerKey.MEDIA_SERVER_CACHE_NAME;
  25 +
  26 + /**
  27 + * 添加一个点播/回放的事务信息 后续可以通过流Id/callID
  28 + *
  29 + * @param cameraCode 设备ID
  30 + * @param channelId 通道ID
  31 + * @param callId 一次请求的CallID
  32 + * @param stream 流名称
  33 + * @param mediaServerId 所使用的流媒体ID
  34 + */
  35 + public void put(
  36 + String cameraCode,
  37 + String channelId,
  38 + String callId,
  39 + String stream,
  40 + String ssrc,
  41 + SipMessageHeaderDTO sipMessage,
  42 + String mediaServerId,
  43 + SessionTypeEnum type) {
  44 +
  45 + SsrcTransactionDTO ssrcTransaction = new SsrcTransactionDTO();
  46 + ssrcTransaction.setCameraCode(cameraCode);
  47 + ssrcTransaction.setChannelId(channelId);
  48 + ssrcTransaction.setStream(stream);
  49 + ssrcTransaction.setSipTransactionInfo(sipMessage);
  50 + ssrcTransaction.setCallId(callId);
  51 + ssrcTransaction.setSsrc(ssrc);
  52 + ssrcTransaction.setMediaServerId(mediaServerId);
  53 + ssrcTransaction.setType(type);
  54 + String fullKey = buildTransactionFullKey(cameraCode, channelId, callId, stream);
  55 + String channelKey = buildTransactionChannelKey(cameraCode, channelId, stream);
  56 + String streamKey = buildTransactionStreamKey(stream);
  57 + String cameraKey = buildTransactionCameraKey(cameraCode);
  58 +
  59 + freshTransactionKey(cameraKey, fullKey, false);
  60 + freshTransactionKey(streamKey, fullKey, false);
  61 + freshTransactionKey(channelKey, fullKey, false);
  62 + cacheUtils.put(cacheName, fullKey, ssrcTransaction);
  63 + }
  64 +
  65 + private void freshTransactionKey(String key, String val, boolean remove) {
  66 + Optional<Set<String>> cameraDatas = cacheUtils.get(cacheName, key);
  67 + Set<String> values = new HashSet<>();
  68 + cameraDatas.ifPresent(values::addAll);
  69 + if (remove) {
  70 + values.remove(val);
  71 + } else {
  72 + values.add(val);
  73 + }
  74 + cacheUtils.put(cacheName, key, values);
  75 + }
  76 +
  77 + public Optional<Set<String>> getSsrcTransactionCamaraKey(String cameraCode) {
  78 + return cacheUtils.get(cacheName, buildTransactionCameraKey(cameraCode));
  79 + }
  80 +
  81 + public Optional<Set<String>> getSsrcTransactionStreamKey(String stream) {
  82 + return cacheUtils.get(cacheName, buildTransactionStreamKey(stream));
  83 + }
  84 +
  85 + public Optional<Set<String>> getSsrcTransactionChannelKey(
  86 + String cameraCode, String channelId, String stream) {
  87 + return cacheUtils.get(cacheName, buildTransactionChannelKey(cameraCode, channelId, stream));
  88 + }
  89 +
  90 + public Optional<SsrcTransactionDTO> getSsrcTransaction(String fullKey) {
  91 + return cacheUtils.get(cacheName, fullKey);
  92 + }
  93 + public Optional<SsrcTransactionDTO> getSsrcTransaction(String cameraCode, String channelId, String stream) {
  94 + Optional<Set<String>> keys =getSsrcTransactionChannelKey(cameraCode, channelId,stream);
  95 +
  96 + if(keys.isPresent()){
  97 + Optional<String> fullKey =keys.get().stream().findFirst();
  98 + if(fullKey.isPresent()){
  99 + return getSsrcTransaction(fullKey.get());
  100 + }
  101 + }
  102 + return Optional.empty();
  103 + }
  104 +
  105 + public String getMediaServerId(String deviceId, String channelId, String stream) {
  106 + Optional<Set<String>> channels = getSsrcTransactionChannelKey(deviceId, channelId, stream);
  107 + if (channels.isPresent()) {
  108 + for (String fullKey : channels.get()) {
  109 + Optional<SsrcTransactionDTO> result = getSsrcTransaction(fullKey);
  110 + if (result.isPresent()) {
  111 + return result.get().getMediaServerId();
  112 + }
  113 + }
  114 + }
  115 + return null;
  116 + }
  117 +
  118 + public String getSSRC(String deviceId, String channelId, String stream) {
  119 + Optional<Set<String>> channels = getSsrcTransactionChannelKey(deviceId, channelId, stream);
  120 +
  121 + if (channels.isPresent()) {
  122 + for (String fullKey : channels.get()) {
  123 + Optional<SsrcTransactionDTO> result = getSsrcTransaction(fullKey);
  124 + if (result.isPresent()) {
  125 + return result.get().getSsrc();
  126 + }
  127 + }
  128 + }
  129 + return null;
  130 + }
  131 +
  132 + public void remove(String cameraCode, String channelId, String stream) {
  133 + Optional<Set<String>> channelKeys = getSsrcTransactionChannelKey(cameraCode, channelId, stream);
  134 + channelKeys.ifPresent(
  135 + keys -> {
  136 + keys.forEach(
  137 + fullKey -> {
  138 + String streamKey = buildTransactionStreamKey(stream);
  139 + String cameraKey = buildTransactionCameraKey(cameraCode);
  140 + String channelKey = buildTransactionChannelKey(cameraCode, channelId, stream);
  141 + freshTransactionKey(cameraKey, fullKey, true);
  142 + freshTransactionKey(streamKey, fullKey, true);
  143 + freshTransactionKey(channelKey, fullKey, true);
  144 + cacheUtils.invalidate(cacheName, fullKey);
  145 + });
  146 + });
  147 + }
  148 +
  149 + public String buildTransactionFullKey(
  150 + String cameraCode, String channelId, String callId, String stream) {
  151 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  152 + + userSetting.getServerId()
  153 + + "_"
  154 + + cameraCode
  155 + + "_"
  156 + + channelId
  157 + + "_"
  158 + + callId
  159 + + "_"
  160 + + stream;
  161 + }
  162 +
  163 + public String buildTransactionChannelKey(String cameraCode, String channelId, String stream) {
  164 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  165 + + userSetting.getServerId()
  166 + + "_"
  167 + + cameraCode
  168 + + "_"
  169 + + channelId
  170 + + "_"
  171 + + stream;
  172 + }
  173 +
  174 + public String buildTransactionStreamKey(String stream) {
  175 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  176 + + userSetting.getServerId()
  177 + + "_"
  178 + + stream;
  179 + }
  180 +
  181 + public String buildTransactionCameraKey(String cameraCode) {
  182 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  183 + + userSetting.getServerId()
  184 + + "_"
  185 + + cameraCode;
  186 + }
  187 +
  188 + public String getReceiveRtpKey(String ssrcStream) {
  189 + return FastIotConstants.CacheSipKey.TK_OTHER_RECEIVE_RTP_INFO
  190 + + userSetting.getServerId()
  191 + + "_"
  192 + + ssrcStream;
  193 + }
  194 +
  195 + public String getReceivePsKey(String ssrcStream) {
  196 + return FastIotConstants.CacheSipKey.TK_OTHER_RECEIVE_PS_INFO
  197 + + userSetting.getServerId()
  198 + + "_"
  199 + + ssrcStream;
  200 + }
  201 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +import java.time.Instant;
  4 +import java.time.LocalDateTime;
  5 +import java.time.ZoneId;
  6 +import java.util.*;
  7 +import java.util.concurrent.ConcurrentHashMap;
  8 +import java.util.concurrent.TimeUnit;
  9 +
  10 +import com.fasterxml.jackson.databind.JsonNode;
  11 +import com.fasterxml.jackson.databind.node.ObjectNode;
  12 +import org.springframework.scheduling.annotation.Scheduled;
  13 +import org.springframework.stereotype.Component;
  14 +import org.springframework.util.CollectionUtils;
  15 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  16 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.IHookSubscribe;
  17 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  18 +
  19 +/**
  20 + * ZLMediaServer的hook事件订阅
  21 + *
  22 + * @author lin
  23 + */
  24 +@Component
  25 +public class ZlmHttpHookSubscribe {
  26 +
  27 + @FunctionalInterface
  28 + public interface Event {
  29 + void response(MediaServerDTO mediaServerInfo, JsonNode response);
  30 + }
  31 +
  32 + private Map<HookTypeEnum, Map<IHookSubscribe, Event>> allSubscribes = new ConcurrentHashMap<>();
  33 +
  34 + public void addSubscribe(IHookSubscribe hookSubscribe, Event event) {
  35 + if (hookSubscribe.getExpires() == null) {
  36 + // 默认5分钟过期
  37 + Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(5));
  38 + hookSubscribe.setExpires(LocalDateTime.ofInstant(expiresInstant, ZoneId.systemDefault()));
  39 + }
  40 + allSubscribes
  41 + .computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>())
  42 + .put(hookSubscribe, event);
  43 + }
  44 +
  45 + public Event sendNotify(HookTypeEnum type, JsonNode hookResponse) {
  46 + Event event = null;
  47 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
  48 + if (eventMap == null) {
  49 + return null;
  50 + }
  51 + for (IHookSubscribe key : eventMap.keySet()) {
  52 + Boolean result = null;
  53 + // 使用 fields() 方法获取键值对迭代器
  54 + Iterator<Map.Entry<String, JsonNode>> fieldsIterator = key.getContent().fields();
  55 + while (fieldsIterator.hasNext()) {
  56 + Map.Entry<String, JsonNode> field = fieldsIterator.next();
  57 + String s = field.getKey();
  58 + JsonNode value = field.getValue();
  59 + if (result == null) {
  60 + result = value.equals(hookResponse.get(s));
  61 + } else {
  62 + if (value == null) {
  63 + continue;
  64 + }
  65 + result = result && value.equals(hookResponse.get(s));
  66 + }
  67 + }
  68 + if (null != result && result) {
  69 + event = eventMap.get(key);
  70 + }
  71 + }
  72 + return event;
  73 + }
  74 +
  75 + public void removeSubscribe(IHookSubscribe hookSubscribe) {
  76 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(hookSubscribe.getHookType());
  77 + if (eventMap == null) {
  78 + return;
  79 + }
  80 +
  81 + Set<Map.Entry<IHookSubscribe, Event>> entries = eventMap.entrySet();
  82 + if (entries.size() > 0) {
  83 + List<Map.Entry<IHookSubscribe, Event>> entriesToRemove = new ArrayList<>();
  84 + for (Map.Entry<IHookSubscribe, Event> entry : entries) {
  85 + ObjectNode content = entry.getKey().getContent();
  86 + if (content == null || content.size() == 0) {
  87 + entriesToRemove.add(entry);
  88 + continue;
  89 + }
  90 + Boolean result = null;
  91 + Iterator<Map.Entry<String, JsonNode>> fieldsIterator = content.fields();
  92 + while (fieldsIterator.hasNext()) {
  93 + Map.Entry<String, JsonNode> field = fieldsIterator.next();
  94 + String s = field.getKey();
  95 + JsonNode value = field.getValue();
  96 + if (result == null) {
  97 + result = value.equals(hookSubscribe.getContent().get(s));
  98 + } else {
  99 + if (value == null) {
  100 + continue;
  101 + }
  102 + result = result && value.equals(hookSubscribe.getContent().get(s));
  103 + }
  104 + }
  105 + if (result) {
  106 + entriesToRemove.add(entry);
  107 + }
  108 + }
  109 +
  110 + if (!CollectionUtils.isEmpty(entriesToRemove)) {
  111 + for (Map.Entry<IHookSubscribe, Event> entry : entriesToRemove) {
  112 + entries.remove(entry);
  113 + }
  114 + }
  115 + }
  116 + }
  117 +
  118 + /**
  119 + * 获取某个类型的所有的订阅
  120 + *
  121 + * @param type
  122 + * @return
  123 + */
  124 + public List<Event> getSubscribes(HookTypeEnum type) {
  125 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
  126 + if (eventMap == null) {
  127 + return null;
  128 + }
  129 + List<Event> result = new ArrayList<>();
  130 + for (IHookSubscribe key : eventMap.keySet()) {
  131 + result.add(eventMap.get(key));
  132 + }
  133 + return result;
  134 + }
  135 +
  136 + public List<IHookSubscribe> getAll() {
  137 + ArrayList<IHookSubscribe> result = new ArrayList<>();
  138 + Collection<Map<IHookSubscribe, Event>> values = allSubscribes.values();
  139 + for (Map<IHookSubscribe, Event> value : values) {
  140 + result.addAll(value.keySet());
  141 + }
  142 + return result;
  143 + }
  144 +
  145 + /** 对订阅数据进行过期清理 */
  146 + @Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行一次
  147 + public void execute() {
  148 +
  149 + Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5));
  150 + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
  151 + int total = 0;
  152 + for (HookTypeEnum hookType : allSubscribes.keySet()) {
  153 + Map<IHookSubscribe, Event> hookSubscribeEventMap = allSubscribes.get(hookType);
  154 + if (hookSubscribeEventMap.size() > 0) {
  155 + for (IHookSubscribe hookSubscribe : hookSubscribeEventMap.keySet()) {
  156 + if (hookSubscribe.getExpires().isBefore(localDateTime)) {
  157 + // 过期的
  158 + hookSubscribeEventMap.remove(hookSubscribe);
  159 + total++;
  160 + }
  161 + }
  162 + }
  163 + }
  164 + }
  165 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.beans.factory.annotation.Value;
  6 +import org.springframework.boot.context.properties.ConfigurationProperties;
  7 +import org.springframework.stereotype.Component;
  8 +import org.springframework.util.ObjectUtils;
  9 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  10 +
  11 +import java.net.InetAddress;
  12 +import java.net.UnknownHostException;
  13 +import java.util.Objects;
  14 +import java.util.regex.Pattern;
  15 +
  16 +/**
  17 + * 配置文件里面的流媒体配置信息
  18 + */
  19 +@ConfigurationProperties(prefix = "media")
  20 +@Component
  21 +@Data
  22 +@Slf4j
  23 +public class MediaConfig {
  24 + @Value("${media.id}")
  25 + private String mediaServerId;
  26 +
  27 + @Value("${media.ip}")
  28 + private String ip;
  29 +
  30 + @Value("${media.hook-ip:}")
  31 + private String hookIp;
  32 +
  33 + @Value("${sip.ip}")
  34 + private String sipIp;
  35 +
  36 + @Value("${sip.domain}")
  37 + private String sipDomain;
  38 +
  39 + @Value("${media.sdp-ip:${media.ip}}")
  40 + private String sdpIp;
  41 +
  42 + @Value("${media.stream-ip:${media.ip}}")
  43 + private String streamIp;
  44 +
  45 + @Value("${media.http-port}")
  46 + private Integer httpPort;
  47 +
  48 + @Value("${media.http-ssl-port:0}")
  49 + private Integer httpSslPort = 0;
  50 +
  51 + @Value("${media.rtmp-port:0}")
  52 + private Integer rtmpPort = 0;
  53 +
  54 + @Value("${media.rtmp-ssl-port:0}")
  55 + private Integer rtmpSslPort = 0;
  56 +
  57 + @Value("${media.rtp-proxy-port:0}")
  58 + private Integer rtpProxyPort = 0;
  59 +
  60 + @Value("${media.rtsp-port:0}")
  61 + private Integer rtspPort = 0;
  62 +
  63 + @Value("${media.rtsp-ssl-port:0}")
  64 + private Integer rtspSslPort = 0;
  65 +
  66 + @Value("${media.auto-config:true}")
  67 + private boolean autoConfig = true;
  68 +
  69 + @Value("${media.secret}")
  70 + private String secret;
  71 +
  72 + @Value("${media.rtp.enable}")
  73 + private boolean rtpEnable;
  74 +
  75 + @Value("${media.rtp.port-range}")
  76 + private String rtpPortRange;
  77 +
  78 + @Value("${media.rtp.send-port-range}")
  79 + private String rtpSendPortRange;
  80 +
  81 + @Value("${media.record-assist-port:0}")
  82 + private Integer recordAssistPort = 0;
  83 + private boolean status;
  84 +
  85 + public MediaServerDTO getMediaSerItem() {
  86 + MediaServerDTO mediaServerItem = new MediaServerDTO();
  87 + mediaServerItem.setMediaServerId(mediaServerId);
  88 + mediaServerItem.setIp(ip);
  89 + mediaServerItem.setDefaultServer(true);
  90 + mediaServerItem.setHookIp(getHookIp());
  91 + mediaServerItem.setSdpIp(getSdpIp());
  92 + mediaServerItem.setStreamIp(getStreamIp());
  93 + mediaServerItem.setHttpPort(httpPort);
  94 + mediaServerItem.setHttpSslPort(httpSslPort);
  95 + mediaServerItem.setRtmpPort(rtmpPort);
  96 + mediaServerItem.setRtmpSslPort(rtmpSslPort);
  97 + mediaServerItem.setRtpProxyPort(getRtpProxyPort());
  98 + mediaServerItem.setRtspPort(rtspPort);
  99 + mediaServerItem.setRtspSslPort(rtspSslPort);
  100 + mediaServerItem.setAutoConfig(autoConfig);
  101 + mediaServerItem.setSecret(secret);
  102 + mediaServerItem.setRtpEnable(rtpEnable);
  103 + mediaServerItem.setRtpPortRange(rtpPortRange);
  104 + mediaServerItem.setSendRtpPortRange(rtpSendPortRange);
  105 + mediaServerItem.setRecordAssistPort(recordAssistPort);
  106 + mediaServerItem.setHookAliveInterval(30.00f);
  107 + mediaServerItem.setStatus(status);
  108 + return mediaServerItem;
  109 + }
  110 +
  111 + public String getHookIp() {
  112 + if (ObjectUtils.isEmpty(hookIp)) {
  113 + return sipIp.split(",")[0];
  114 + } else {
  115 + return hookIp;
  116 + }
  117 + }
  118 +
  119 + public String getSipIp() {
  120 + if (sipIp == null) {
  121 + return this.ip;
  122 + } else {
  123 + return sipIp;
  124 + }
  125 + }
  126 +
  127 + public int getRtpProxyPort() {
  128 + return Objects.requireNonNullElse(rtpProxyPort, 0);
  129 + }
  130 +
  131 + public String getSdpIp() {
  132 + if (ObjectUtils.isEmpty(sdpIp)) {
  133 + return ip;
  134 + } else {
  135 + if (isValidIPAddress(sdpIp)) {
  136 + return sdpIp;
  137 + } else {
  138 + // 按照域名解析
  139 + String hostAddress = null;
  140 + try {
  141 + hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
  142 + } catch (UnknownHostException e) {
  143 + log.error("[获取SDP IP]: 域名解析失败");
  144 + }
  145 + return hostAddress;
  146 + }
  147 + }
  148 + }
  149 +
  150 + public String getStreamIp() {
  151 + if (ObjectUtils.isEmpty(streamIp)) {
  152 + return ip;
  153 + } else {
  154 + return streamIp;
  155 + }
  156 + }
  157 +
  158 + private boolean isValidIPAddress(String ipAddress) {
  159 + if ((ipAddress != null) && (!ipAddress.isEmpty())) {
  160 + return Pattern.matches(
  161 + "^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$",
  162 + ipAddress);
  163 + }
  164 + return false;
  165 + }
  166 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@ConfigurationProperties(prefix = "sip")
  8 +@Component
  9 +@Data
  10 +public class SipConfig {
  11 + private String ip;
  12 + private String showIp;
  13 + private Integer port;
  14 + private String id;
  15 + private String domain;
  16 + private String password;
  17 + Integer ptzSpeed = 50;
  18 + Integer registerTimeInterval = 120;
  19 + private boolean alarm;
  20 +
  21 + public String getShowIp() {
  22 + if (this.showIp == null) {
  23 + return this.ip;
  24 + }
  25 + return showIp;
  26 + }
  27 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.beans.factory.annotation.Value;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@Component
  8 +@Data
  9 +public class ThingsKitVersionConfig {
  10 + @Value("${thingskit.release.version}")
  11 + private String releaseVersion;
  12 +
  13 + @Value("${thingskit.release.date}")
  14 + private String releaseDate;
  15 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@ConfigurationProperties(prefix = "user-settings")
  8 +@Component
  9 +@Data
  10 +public class UserSetting {
  11 + private String serverId = "000000";
  12 + private Boolean seniorSdp = Boolean.FALSE;
  13 + private Boolean pushAuthority = Boolean.TRUE;
  14 + private Boolean recordSip = Boolean.TRUE;
  15 + private Boolean recordPushLive = Boolean.TRUE;
  16 + private String recordPath = null;
  17 + private Boolean streamOnDemand = Boolean.TRUE;
  18 + private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE;
  19 + private Integer playTimeout = 18000;
  20 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +/** ZLMediaKit流媒体配置文件,具体配置说明可以参考ZLMediaKit的config.ini文件 */
  7 +@Data
  8 +public class ZLMediaKitServerConfig {
  9 + @JsonProperty(value = "api.apiDebug")
  10 + private String apiDebug;
  11 +
  12 + @JsonProperty(value = "api.secret")
  13 + private String apiSecret;
  14 +
  15 + @JsonProperty(value = "api.snapRoot")
  16 + private String apiSnapRoot;
  17 +
  18 + @JsonProperty(value = "api.defaultSnap")
  19 + private String apiDefaultSnap;
  20 +
  21 + @JsonProperty(value = "ffmpeg.bin")
  22 + private String ffmpegBin;
  23 +
  24 + @JsonProperty(value = "ffmpeg.cmd")
  25 + private String ffmpegCmd;
  26 +
  27 + @JsonProperty(value = "ffmpeg.snap")
  28 + private String ffmpegSnap;
  29 +
  30 + @JsonProperty(value = "ffmpeg.log")
  31 + private String ffmpegLog;
  32 +
  33 + @JsonProperty(value = "ffmpeg.restart_sec")
  34 + private String ffmpegRestartSec;
  35 +
  36 + @JsonProperty(value = "protocol.modify_stamp")
  37 + private String protocolModifyStamp;
  38 +
  39 + @JsonProperty(value = "protocol.enable_audio")
  40 + private String protocolEnableAudio;
  41 +
  42 + @JsonProperty(value = "protocol.add_mute_audio")
  43 + private String protocolAddMuteAudio;
  44 +
  45 + @JsonProperty(value = "protocol.continue_push_ms")
  46 + private String protocolContinuePushMs;
  47 +
  48 + @JsonProperty(value = "protocol.enable_hls")
  49 + private String protocolEnableHls;
  50 +
  51 + @JsonProperty(value = "protocol.enable_mp4")
  52 + private String protocolEnableMp4;
  53 +
  54 + @JsonProperty(value = "protocol.enable_rtsp")
  55 + private String protocolEnableRtsp;
  56 +
  57 + @JsonProperty(value = "protocol.enable_rtmp")
  58 + private String protocolEnableRtmp;
  59 +
  60 + @JsonProperty(value = "protocol.enable_ts")
  61 + private String protocolEnableTs;
  62 +
  63 + @JsonProperty(value = "protocol.enable_fmp4")
  64 + private String protocolEnableFmp4;
  65 +
  66 + @JsonProperty(value = "protocol.mp4_as_player")
  67 + private String protocolMp4AsPlayer;
  68 +
  69 + @JsonProperty(value = "protocol.mp4_max_second")
  70 + private String protocolMp4MaxSecond;
  71 +
  72 + @JsonProperty(value = "protocol.mp4_save_path")
  73 + private String protocolMp4SavePath;
  74 +
  75 + @JsonProperty(value = "protocol.hls_save_path")
  76 + private String protocolHlsSavePath;
  77 +
  78 + @JsonProperty(value = "protocol.hls_demand")
  79 + private String protocolHlsDemand;
  80 +
  81 + @JsonProperty(value = "protocol.rtsp_demand")
  82 + private String protocolRtspDemand;
  83 +
  84 + @JsonProperty(value = "protocol.rtmp_demand")
  85 + private String protocolRtmpDemand;
  86 +
  87 + @JsonProperty(value = "protocol.ts_demand")
  88 + private String protocolTsDemand;
  89 +
  90 + @JsonProperty(value = "protocol.fmp4_demand")
  91 + private String protocolFmp4Demand;
  92 +
  93 + @JsonProperty(value = "general.enableVhost")
  94 + private String generalEnableVhost;
  95 +
  96 + @JsonProperty(value = "general.flowThreshold")
  97 + private String generalFlowThreshold;
  98 +
  99 + @JsonProperty(value = "general.maxStreamWaitMS")
  100 + private String generalMaxStreamWaitMS;
  101 +
  102 + @JsonProperty(value = "general.streamNoneReaderDelayMS")
  103 + private int generalStreamNoneReaderDelayMS;
  104 +
  105 + @JsonProperty(value = "general.resetWhenRePlay")
  106 + private String generalResetWhenRePlay;
  107 +
  108 + @JsonProperty(value = "general.mergeWriteMS")
  109 + private String generalMergeWriteMS;
  110 +
  111 + @JsonProperty(value = "general.mediaServerId")
  112 + private String generalMediaServerId;
  113 +
  114 + @JsonProperty(value = "general.wait_track_ready_ms")
  115 + private String generalWaitTrackReadyMs;
  116 +
  117 + @JsonProperty(value = "general.wait_add_track_ms")
  118 + private String generalWaitAddTrackMs;
  119 +
  120 + @JsonProperty(value = "general.unready_frame_cache")
  121 + private String generalUnreadyFrameCache;
  122 +
  123 + @JsonProperty(value = "hls.fileBufSize")
  124 + private String hlsFileBufSize;
  125 +
  126 + @JsonProperty(value = "hls.filePath")
  127 + private String hlsFilePath;
  128 +
  129 + @JsonProperty(value = "hls.segDur")
  130 + private String hlsSegDur;
  131 +
  132 + @JsonProperty(value = "hls.segNum")
  133 + private String hlsSegNum;
  134 +
  135 + @JsonProperty(value = "hls.segRetain")
  136 + private String hlsSegRetain;
  137 +
  138 + @JsonProperty(value = "hls.broadcastRecordTs")
  139 + private String hlsBroadcastRecordTs;
  140 +
  141 + @JsonProperty(value = "hls.deleteDelaySec")
  142 + private String hlsDeleteDelaySec;
  143 +
  144 + @JsonProperty(value = "hls.segKeep")
  145 + private String hlsSegKeep;
  146 +
  147 + @JsonProperty(value = "hook.access_file_except_hls")
  148 + private String hookAccessFileExceptHLS;
  149 +
  150 + @JsonProperty(value = "hook.admin_params")
  151 + private String hookAdminParams;
  152 +
  153 + @JsonProperty(value = "hook.alive_interval")
  154 + private Float hookAliveInterval;
  155 +
  156 + @JsonProperty(value = "hook.enable")
  157 + private String hookEnable;
  158 +
  159 + @JsonProperty(value = "hook.on_flow_report")
  160 + private String hookOnFlowReport;
  161 +
  162 + @JsonProperty(value = "hook.on_http_access")
  163 + private String hookOnHttpAccess;
  164 +
  165 + @JsonProperty(value = "hook.on_play")
  166 + private String hookOnPlay;
  167 +
  168 + @JsonProperty(value = "hook.on_publish")
  169 + private String hookOnPublish;
  170 +
  171 + @JsonProperty(value = "hook.on_record_mp4")
  172 + private String hookOnRecordMp4;
  173 +
  174 + @JsonProperty(value = "hook.on_rtsp_auth")
  175 + private String hookOnRtspAuth;
  176 +
  177 + @JsonProperty(value = "hook.on_rtsp_realm")
  178 + private String hookOnRtspRealm;
  179 +
  180 + @JsonProperty(value = "hook.on_shell_login")
  181 + private String hookOnShellLogin;
  182 +
  183 + @JsonProperty(value = "hook.on_stream_changed")
  184 + private String hookOnStreamChanged;
  185 +
  186 + @JsonProperty(value = "hook.on_stream_none_reader")
  187 + private String hookOnStreamNoneReader;
  188 +
  189 + @JsonProperty(value = "hook.on_stream_not_found")
  190 + private String hookOnStreamNotFound;
  191 +
  192 + @JsonProperty(value = "hook.on_server_started")
  193 + private String hookOnServerStarted;
  194 +
  195 + @JsonProperty(value = "hook.on_server_keepalive")
  196 + private String hookOnServerKeepalive;
  197 +
  198 + @JsonProperty(value = "hook.on_send_rtp_stopped")
  199 + private String hookOnSendRtpStopped;
  200 +
  201 + @JsonProperty(value = "hook.on_rtp_server_timeout")
  202 + private String hookOnRtpServerTimeout;
  203 +
  204 + @JsonProperty(value = "hook.timeoutSec")
  205 + private String hookTimeoutSec;
  206 +
  207 + @JsonProperty(value = "http.charSet")
  208 + private String httpCharSet;
  209 +
  210 + @JsonProperty(value = "http.keepAliveSecond")
  211 + private String httpKeepAliveSecond;
  212 +
  213 + @JsonProperty(value = "http.maxReqCount")
  214 + private String httpMaxReqCount;
  215 +
  216 + @JsonProperty(value = "http.maxReqSize")
  217 + private String httpMaxReqSize;
  218 +
  219 + @JsonProperty(value = "http.notFound")
  220 + private String httpNotFound;
  221 +
  222 + @JsonProperty(value = "http.port")
  223 + private int httpPort;
  224 +
  225 + @JsonProperty(value = "http.rootPath")
  226 + private String httpRootPath;
  227 +
  228 + @JsonProperty(value = "http.sendBufSize")
  229 + private String httpSendBufSize;
  230 +
  231 + @JsonProperty(value = "http.sslport")
  232 + private int httpSslPort;
  233 +
  234 + @JsonProperty(value = "multicast.addrMax")
  235 + private String multicastAddrMax;
  236 +
  237 + @JsonProperty(value = "multicast.addrMin")
  238 + private String multicastAddrMin;
  239 +
  240 + @JsonProperty(value = "multicast.udpTTL")
  241 + private String multicastUdpTTL;
  242 +
  243 + @JsonProperty(value = "record.appName")
  244 + private String recordAppName;
  245 +
  246 + @JsonProperty(value = "record.filePath")
  247 + private String recordFilePath;
  248 +
  249 + @JsonProperty(value = "record.fileSecond")
  250 + private String recordFileSecond;
  251 +
  252 + @JsonProperty(value = "record.sampleMS")
  253 + private String recordFileSampleMS;
  254 +
  255 + @JsonProperty(value = "rtmp.handshakeSecond")
  256 + private String rtmpHandshakeSecond;
  257 +
  258 + @JsonProperty(value = "rtmp.keepAliveSecond")
  259 + private String rtmpKeepAliveSecond;
  260 +
  261 + @JsonProperty(value = "rtmp.modifyStamp")
  262 + private String rtmpModifyStamp;
  263 +
  264 + @JsonProperty(value = "rtmp.port")
  265 + private int rtmpPort;
  266 +
  267 + @JsonProperty(value = "rtmp.sslport")
  268 + private int rtmpSslPort;
  269 +
  270 + @JsonProperty(value = "rtp.audioMtuSize")
  271 + private String rtpAudioMtuSize;
  272 +
  273 + @JsonProperty(value = "rtp.clearCount")
  274 + private String rtpClearCount;
  275 +
  276 + @JsonProperty(value = "rtp.cycleMS")
  277 + private String rtpCycleMS;
  278 +
  279 + @JsonProperty(value = "rtp.maxRtpCount")
  280 + private String rtpMaxRtpCount;
  281 +
  282 + @JsonProperty(value = "rtp.videoMtuSize")
  283 + private String rtpVideoMtuSize;
  284 +
  285 + @JsonProperty(value = "rtp_proxy.checkSource")
  286 + private String rtpProxyCheckSource;
  287 +
  288 + @JsonProperty(value = "rtp_proxy.dumpDir")
  289 + private String rtpProxyDumpDir;
  290 +
  291 + @JsonProperty(value = "rtp_proxy.port")
  292 + private int rtpProxyPort;
  293 +
  294 + @JsonProperty(value = "rtp_proxy.port_range")
  295 + private String portRange;
  296 +
  297 + @JsonProperty(value = "rtp_proxy.timeoutSec")
  298 + private String rtpProxyTimeoutSec;
  299 +
  300 + @JsonProperty(value = "rtsp.authBasic")
  301 + private String rtspAuthBasic;
  302 +
  303 + @JsonProperty(value = "rtsp.handshakeSecond")
  304 + private String rtspHandshakeSecond;
  305 +
  306 + @JsonProperty(value = "rtsp.keepAliveSecond")
  307 + private String rtspKeepAliveSecond;
  308 +
  309 + @JsonProperty(value = "rtsp.port")
  310 + private int rtspPort;
  311 +
  312 + @JsonProperty(value = "rtsp.sslport")
  313 + private int rtspSslPort;
  314 +
  315 + @JsonProperty(value = "shell.maxReqSize")
  316 + private String shellMaxReqSize;
  317 +
  318 + @JsonProperty(value = "shell.port")
  319 + private String shellPort;
  320 +
  321 + @JsonProperty(value = "ip")
  322 + private String ip;
  323 +
  324 + private String sdpIp;
  325 +
  326 + private String streamIp;
  327 +
  328 + private String hookIp;
  329 + private String tenantId;
  330 +}
... ...
... ... @@ -51,7 +51,6 @@ public interface FastIotConstants {
51 51 * 操作功能码类型
52 52 */
53 53 String OPERATION_TYPE = "operationType";
54   -
55 54 /**
56 55 * 缩放因子
57 56 */
... ... @@ -67,6 +66,146 @@ public interface FastIotConstants {
67 66 */
68 67 String BIT_MASK = "bitMask";
69 68 }
  69 + interface CacheSipKey {
  70 + String TK_SIP_CACHE_NAME = "tkSipCacheName";
  71 + String TK_OTHER_RECEIVE_RTP_INFO = "TK_OTHER_RECEIVE_RTP_INFO_";
  72 +
  73 + String TK_OTHER_RECEIVE_PS_INFO = "TK_OTHER_RECEIVE_PS_INFO_";
  74 + }
  75 +
  76 + interface MediaServerKey {
  77 +
  78 + String MEDIA_SERVER_CACHE_NAME = "tkMediaServerCacheName";
  79 + String MEDIA_SERVER_PREFIX = "TK_MEDIA_SERVER_";
  80 + String MEDIA_TRANSACTION_USED_PREFIX = "TK_MEDIA_TRANSACTION_";
  81 + String MEDIA_SERVERS_ONLINE_PREFIX = "TK_MEDIA_ONLINE_SERVERS_";
  82 + String MEDIA_STREAM_AUTHORITY = "TK_MEDIA_STREAM_AUTHORITY_";
  83 +
  84 + /** 点播信息的缓存键 */
  85 + String PLAYER_PREFIX = "TK_PLAYER";
  86 +
  87 + /** 回放信息的缓存键 */
  88 + String PLAY_BACK_PREFIX = "TK_PLAY_BACK";
  89 +
  90 + String TK_SERVER_STREAM_PREFIX = "TK_SIGNALLING_STREAM_";
  91 +
  92 + String PLATFORM_SEND_RTP_INFO_PREFIX = "PLATFORM_SEND_RTP_INFO_";
  93 + }
  94 +
  95 + interface ZLMediaKitHttpApi {
  96 + String BASE_URL = "/index/api";
  97 +
  98 + /** 获取流列表,可选筛选参数 */
  99 + String GET_MEDIA_LIST = BASE_URL + "/getMediaList";
  100 +
  101 + /** 获取rtp代理时的某路ssrc rtp信息 */
  102 + String GET_RTP_INFO = BASE_URL + "/getRtpInfo";
  103 +
  104 + /** 获取视频流截图 */
  105 + String GET_STREAM_SNAP = BASE_URL + "/getSnap";
  106 +
  107 + /** 通过fork FFmpeg进程的方式拉流代理,支持任意协议 */
  108 + String ADD_FFMPEG_SOURCE = BASE_URL + "/addFFmpegSource";
  109 +
  110 + /** 关闭ffmpeg拉流代理(流注册成功后,也可以使用close_streams接口替代) */
  111 + String DEL_FFMPEG_SOURCE = BASE_URL + "/delFFmpegSource";
  112 +
  113 + /** 获取服务器配置 */
  114 + String GET_SERVER_CONFIG = BASE_URL + "/getServerConfig";
  115 +
  116 + /** 设置服务器配置 */
  117 + String SET_SERVER_CONFIG = BASE_URL + "/setServerConfig";
  118 +
  119 + /** 创建GB28181 RTP接收端口,如果该端口接收数据超时,则会自动被回收(不用调用closeRtpServer接口) */
  120 + String OPEN_RTP_SERVER = BASE_URL + "/openRtpServer";
  121 +
  122 + /** 关闭GB28181 RTP接收端口 */
  123 + String CLOSE_RTP_SERVER = BASE_URL + "/closeRtpServer";
  124 +
  125 + /**
  126 + * 作为GB28181客户端,启动ps-rtp推流,支持rtp/udp方式;该接口支持rtsp/rtmp等协议转ps-rtp推流。
  127 + * 第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试。
  128 + */
  129 + String START_SEND_RTP = BASE_URL + "/startSendRtp";
  130 +
  131 + /** 停止GB28181 ps-rtp推流 */
  132 + String STOP_SEND_RTP = BASE_URL + "/stopSendRtp";
  133 +
  134 + /** 重启服务器,只有Daemon方式才能重启,否则是直接关闭! */
  135 + String RESTART_SERVER = BASE_URL + "/restartServer";
  136 + }
  137 +
  138 + interface ZLMediaBody {
  139 + /** 表单数据:异常码 */
  140 + String CODE = "code";
  141 +
  142 + /** 表单数据:数据 */
  143 + String DATA = "data";
  144 +
  145 + /** 表单数据:异常消息 */
  146 + String MSG = "msg";
  147 +
  148 + /** 表单数据:流媒体密钥 */
  149 + String SECRET = "secret";
  150 +
  151 + /** 流媒体服务器信息 */
  152 + String MEDIA = "media";
  153 +
  154 + /** 流媒体服务器信息:ID */
  155 + String MEDIA_ID = "mediaId";
  156 +
  157 + /** 流媒体服务器信息:会话描述协议 */
  158 + String MEDIA_SDP_IP = "sdpIp";
  159 +
  160 + /** 数据流服务ID */
  161 + String SSRCINFO_STREAM = "streamId";
  162 +
  163 + /** 数据流服务的端口 */
  164 + String SSRCINFO_PORT = "streamPort";
  165 +
  166 + /** 同步源标识符 */
  167 + String SSRCINFO_SSRC = "ssrc";
  168 +
  169 + /** 流媒体通道ID */
  170 + String CHANNEL_ID = "channelId";
  171 +
  172 + /** 流媒体通道ID */
  173 + String CALL_ID = "callId";
  174 +
  175 + /** 设备国标编号 */
  176 + String CAMERA_CODE = "cameraCode";
  177 +
  178 + /** 方法类型 */
  179 + String METHOD_TYPE = "methodType";
  180 +
  181 + /** 请求响应事件:头部标签内容 */
  182 + String MSG_HEADER = "msgHeader";
  183 +
  184 + /** 请求响应事件:表单内容 */
  185 + String MSG_CONTEXT = "msgContext";
  186 +
  187 + /** 设备控制内容 */
  188 + String CONTROL_CONTEXT = "ptzContext";
  189 +
  190 + /** 订阅处理结果 */
  191 + String SESSION_TYPE = "sessionType";
  192 +
  193 + /** 订阅处理结果 */
  194 + String SSRC_CHECK = "ssrcCheck";
  195 + }
  196 +
  197 + /** 视频流传输模式 */
  198 + interface StreamMode {
  199 + /** tcp 被动模式 */
  200 + String TCP_PASSIVE = "TCP-PASSIVE";
  201 +
  202 + /** tcp 主动模式 */
  203 + String TCP_ACTIVE = "TCP-ACTIVE";
  204 +
  205 + /** udp */
  206 + String UDP = "UDP";
  207 + }
  208 +
70 209 interface TBCacheConfig {
71 210 String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE";
72 211 String EXISTING_TENANT = "EXISTING_TENANT";
... ... @@ -248,4 +387,22 @@ public interface FastIotConstants {
248 387 public static String TK_MSG_EVENT_NODE = "org.thingsboard.rule.engine.yunteng.event.TkMsgEventNode";
249 388 }
250 389
  390 +
  391 + /** 设备扩展信息内容 */
  392 + interface DeviceAdditional {
  393 + /** 摄像头信息 */
  394 + String SIP = "sip";
  395 +
  396 + /** 设备图片 */
  397 + String AVATAR = "avatar";
  398 +
  399 + /** 设备地理位置 */
  400 + String ADDRESS = "address";
  401 +
  402 + /** 地理坐标之经度 */
  403 + String LONGITUDE = "longitude";
  404 +
  405 + /** 地理坐标之维度 */
  406 + String LATITUDE = "latitude";
  407 + }
251 408 }
... ...
... ... @@ -133,6 +133,12 @@ public final class ModelConstants {
133 133
134 134 /** 产品品类表 */
135 135 public static final String TK_DEVICE_PROFILE_CATEGORY = "tk_device_profile_category";
  136 +
  137 + /** ZLMediaKit 流媒体表 */
  138 + public static final String TK_MEDIA_SERVER_NAME = "tk_media_server";
  139 +
  140 + /** 视频通道表 */
  141 + public static final String TK_VIDEO_CHANNEL_NAME = "tk_video_channel";
136 142 }
137 143
138 144 public static class TableFields {
... ...
  1 +package org.thingsboard.server.common.data.yunteng.constant;
  2 +
  3 +public class SipRequestMethodConstants {
  4 + public static final String ACK = "ACK";
  5 + public static final String BYE = "BYE";
  6 + public static final String CANCEL = "CANCEL";
  7 + public static final String INVITE = "INVITE";
  8 + public static final String OPTIONS = "OPTIONS";
  9 + public static final String REGISTER = "REGISTER";
  10 + public static final String NOTIFY = "NOTIFY";
  11 + public static final String SUBSCRIBE = "SUBSCRIBE";
  12 + public static final String MESSAGE = "MESSAGE";
  13 + public static final String REFER = "REFER";
  14 +
  15 + public static final String INFO = "INFO";
  16 + public static final String PRACK = "PRACK";
  17 +
  18 + public static final String UPDATE = "UPDATE";
  19 +
  20 + public static final String PUBLISH = "PUBLISH";
  21 +}
... ...
... ... @@ -120,7 +120,17 @@ public enum ErrorMessage {
120 120 EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"),
121 121 EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"),
122 122 IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"),
123   - IMPORT_ERROR(400098,"请使用模板excel重新导入"),
  123 + SSRC_INFO_NOT_FOUND(400098,"缓存事务信息未找到,设备【%s】 通道【%s】"),
  124 + VIDEO_CHANNEL_NOT_FOUND(400099,"视频通道不存在"),
  125 + ONLINE_MEDIA_SERVER_NOT_FOUND(400100,"没有可用的流媒体节点"),
  126 + FOUND_VIDEO_DEVICE_FAILED(400101,"获取视频设备信息失败"),
  127 + NOT_FOUND_MEDIA_SERVER_FOR_PLAY(400102,"未找到可用于播放的流媒体"),
  128 + RECEIVE_STREAM_FAILED(400103,"开启收流失败"),
  129 + GET_PLAY_PORT_FAILED(400104,"获取点播端口异常"),
  130 + STREAM_INFO_NOT_FOUND_FOR_PLAY(400105,"点播信息未找到"),
  131 + SIP_COMMAND_SEND_FAILED(400106,"sip命令下发失败"),
  132 + NOT_BELONG_CURRENT_CUSTOMER(400107,"该数据不属于当前客户"),
  133 + IMPORT_ERROR(400108,"请使用模板excel重新导入"),
124 134 HAVE_NO_PERMISSION(500002,"没有修改权限"),
125 135 NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置");
126 136
... ...
... ... @@ -26,7 +26,6 @@ public class TkVideoDTO extends TenantDTO {
26 26 private String deviceType;
27 27
28 28 @ApiModelProperty(value = "摄像头编号/监控点位编号", required = true)
29   - @NotEmpty(message = "摄像头编号不能为空或空字符串")
30 29 private String sn;
31 30
32 31 @ApiModelProperty(value = "组织ID", required = true)
... ... @@ -42,7 +41,7 @@ public class TkVideoDTO extends TenantDTO {
42 41 @ApiModelProperty(value = "摄像头描述")
43 42 private String description;
44 43
45   - @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取", required = true)
  44 + @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取 2 GBT28281", required = true)
46 45 private Integer accessMode;
47 46
48 47 @ApiModelProperty(value = "平台ID")
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +import javax.validation.constraints.NotEmpty;
  9 +import java.util.List;
  10 +
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +public class TkVideoGptDTO extends TenantDTO {
  14 +
  15 + @ApiModelProperty(value = "组织ID", required = true)
  16 + @NotEmpty(message = "组织ID不能为空或空字符串")
  17 + private String organizationId;
  18 +
  19 + @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取 2 GBT28281", required = true)
  20 + private Integer accessMode;
  21 +
  22 + @ApiModelProperty(value = "多个设备id与通道号")
  23 + private List<TkVideoGptDeviceDTO> gptDeviceDTOS;
  24 +
  25 +}
\ No newline at end of file
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import java.util.List;
  9 +
  10 +@Data
  11 +public class TkVideoGptDeviceDTO {
  12 +
  13 + @ApiModelProperty(value = "设备id", required = true)
  14 + @NotEmpty(message = "设备id不能为空")
  15 + private String deviceID;
  16 +
  17 + @ApiModelProperty(value = "设备名称", required = true)
  18 + @NotEmpty(message = "设备名称不能为空")
  19 + private String deviceName;
  20 +
  21 + @ApiModelProperty(value = "通道号集合")
  22 + @NotEmpty(message = "通道号集合不能为空")
  23 + private List<String> channelNos;
  24 +
  25 +}
\ No newline at end of file
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.yunteng.enums.CatalogDataStatusEnum;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Data
  10 +public class CatalogDataDTO {
  11 + /** 命令序列号 */
  12 + private int sn;
  13 +
  14 + private int total;
  15 + private List<VideoChanelDTO> channelList;
  16 + private LocalDateTime lastTime;
  17 + private SipDeviceDTO device;
  18 + private String errorMsg;
  19 + private CatalogDataStatusEnum status;
  20 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.springframework.util.ObjectUtils;
  7 +import org.thingsboard.server.common.data.yunteng.config.media.ZLMediaKitServerConfig;
  8 +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO;
  9 +
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@Data
  12 +public class MediaServerDTO extends TenantDTO {
  13 + private static final long serialVersionUID = -2499846994830954839L;
  14 +
  15 + @ApiModelProperty(value = "流媒体的ID,非主键ID")
  16 + private String mediaServerId;
  17 +
  18 + @ApiModelProperty(value = "流媒体的IP")
  19 + private String ip;
  20 +
  21 + @ApiModelProperty(value = "hook使用的IP")
  22 + private String hookIp;
  23 +
  24 + @ApiModelProperty(value = "SDP IP")
  25 + private String sdpIp;
  26 +
  27 + @ApiModelProperty(value = "流IP")
  28 + private String streamIp;
  29 +
  30 + @ApiModelProperty(value = "HTTP端口")
  31 + private Integer httpPort;
  32 +
  33 + @ApiModelProperty(value = "HTTPS端口")
  34 + private Integer httpSslPort;
  35 +
  36 + @ApiModelProperty(value = "RTMP端口")
  37 + private Integer rtmpPort;
  38 +
  39 + @ApiModelProperty(value = "RTMP安全协议端口")
  40 + private Integer rtmpSslPort;
  41 +
  42 + @ApiModelProperty(value = "RTP收流端口(单端口模式有用)")
  43 + private Integer rtpProxyPort;
  44 +
  45 + @ApiModelProperty(value = "RTSP端口")
  46 + private Integer rtspPort;
  47 +
  48 + @ApiModelProperty(value = "RTSP安全协议端口")
  49 + private Integer rtspSslPort;
  50 +
  51 + @ApiModelProperty(value = "是否开启自动配置ZLMediaKit")
  52 + private boolean autoConfig;
  53 +
  54 + @ApiModelProperty(value = "ZLMediaKit鉴权参数")
  55 + private String secret;
  56 +
  57 + @ApiModelProperty(value = "keepalive hook触发间隔,单位秒")
  58 + private Float hookAliveInterval;
  59 +
  60 + @ApiModelProperty(value = "是否使用多端口模式")
  61 + private boolean rtpEnable;
  62 +
  63 + @ApiModelProperty(value = "流媒体服务器的状态")
  64 + private boolean status;
  65 +
  66 + @ApiModelProperty(value = "多端口RTP收流端口范围")
  67 + private String rtpPortRange;
  68 +
  69 + @ApiModelProperty(value = "RTP发流端口范围")
  70 + private String sendRtpPortRange;
  71 +
  72 + @ApiModelProperty(value = "assist服务端口")
  73 + private Integer recordAssistPort;
  74 +
  75 + @ApiModelProperty(value = "最后一次心跳时间")
  76 + private String lastKeepaliveTime;
  77 +
  78 + @ApiModelProperty(value = "是否是默认ZLM")
  79 + private boolean defaultServer;
  80 +
  81 + @ApiModelProperty(value = "当前使用到的端口")
  82 + private Integer currentPort;
  83 +
  84 + public MediaServerDTO(){
  85 +
  86 + }
  87 + public MediaServerDTO(ZLMediaKitServerConfig zlmServerConfig, String sipIp) {
  88 + setId(zlmServerConfig.getGeneralMediaServerId());
  89 + ip = zlmServerConfig.getIp();
  90 + hookIp = ObjectUtils.isEmpty(zlmServerConfig.getHookIp()) ? sipIp : zlmServerConfig.getHookIp();
  91 + sdpIp =
  92 + ObjectUtils.isEmpty(zlmServerConfig.getSdpIp())
  93 + ? zlmServerConfig.getIp()
  94 + : zlmServerConfig.getSdpIp();
  95 + streamIp =
  96 + ObjectUtils.isEmpty(zlmServerConfig.getStreamIp())
  97 + ? zlmServerConfig.getIp()
  98 + : zlmServerConfig.getStreamIp();
  99 + httpPort = zlmServerConfig.getHttpPort();
  100 + httpSslPort = zlmServerConfig.getHttpSslPort();
  101 + rtmpPort = zlmServerConfig.getRtmpPort();
  102 + rtmpSslPort = zlmServerConfig.getRtmpSslPort();
  103 + rtpProxyPort = zlmServerConfig.getRtpProxyPort();
  104 + rtspPort = zlmServerConfig.getRtspPort();
  105 + rtspSslPort = zlmServerConfig.getRtspSslPort();
  106 + autoConfig = true; // 默认值true;
  107 + secret = zlmServerConfig.getApiSecret();
  108 + hookAliveInterval = zlmServerConfig.getHookAliveInterval();
  109 + rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
  110 + rtpPortRange = zlmServerConfig.getPortRange().replace("_", ","); // 默认使用30000,30500作为级联时发送流的端口号
  111 + recordAssistPort = 0; // 默认关闭
  112 + }
  113 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  6 +
  7 +import java.io.Serializable;
  8 +
  9 +@Data
  10 +public class PTZCmdDTO implements Serializable {
  11 +
  12 + @ApiModelProperty(value = "控制指令")
  13 + private PTZCommandEnum command;
  14 +
  15 + @ApiModelProperty(value = "水平速度")
  16 + private int horizonSpeed;
  17 +
  18 + @ApiModelProperty(value = "垂直速度")
  19 + private int verticalSpeed;
  20 +
  21 + @ApiModelProperty(value = "缩放速度")
  22 + private int zoomSpeed;
  23 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.InviteStreamTypeEnum;
  6 +
  7 +@Data
  8 +public class SendRtpItemDTO {
  9 + @ApiModelProperty(value = "推流ip")
  10 + private String ip;
  11 +
  12 + @ApiModelProperty(value = "推流端口")
  13 + private int port;
  14 +
  15 + @ApiModelProperty(value = "推流标识")
  16 + private String ssrc;
  17 +
  18 + @ApiModelProperty(value = "平台id")
  19 + private String platformId;
  20 +
  21 + @ApiModelProperty(value = "对应设备id")
  22 + private String deviceId;
  23 +
  24 + @ApiModelProperty(value = "直播流的应用名")
  25 + private String app;
  26 +
  27 + @ApiModelProperty(value = "通道id")
  28 + private String channelId;
  29 +
  30 + @ApiModelProperty(value = "推流状态:0 等待设备推流上来 1 等待上级平台回复ack 2 推流中")
  31 + private int status = 0;
  32 +
  33 + @ApiModelProperty(value = "设备推流的streamId")
  34 + private String streamId;
  35 +
  36 + @ApiModelProperty(value = "是否为tcp")
  37 + private boolean tcp;
  38 +
  39 + @ApiModelProperty(value = "是否为tcp主动模式")
  40 + private boolean tcpActive;
  41 +
  42 + @ApiModelProperty(value = "自己推流使用的端口")
  43 + private int localPort;
  44 +
  45 + @ApiModelProperty(value = "使用的流媒体")
  46 + private String mediaServerId;
  47 +
  48 + @ApiModelProperty(value = "使用的服务的ID")
  49 + private String serverId;
  50 +
  51 + @ApiModelProperty(value = "invite 的 callId")
  52 + private String CallId;
  53 +
  54 + @ApiModelProperty(value = "invite 的 fromTag")
  55 + private String fromTag;
  56 +
  57 + @ApiModelProperty(value = "invite 的 toTag")
  58 + private String toTag;
  59 +
  60 + @ApiModelProperty(value = "发送时,rtp的pt(uint8_t),不传时默认为96")
  61 + private int pt = 96;
  62 +
  63 + @ApiModelProperty(value = "发送时,rtp的负载类型。为true时,负载为ps;为false时,为es")
  64 + private boolean usePs = true;
  65 +
  66 + @ApiModelProperty(value = "当usePs 为false时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0")
  67 + private boolean onlyAudio = false;
  68 +
  69 + @ApiModelProperty(value = "是否开启rtcp保活")
  70 + private boolean rtcp = false;
  71 +
  72 + @ApiModelProperty(value = "播放类型")
  73 + private InviteStreamTypeEnum playType;
  74 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  6 +
  7 +import java.io.Serializable;
  8 +
  9 +@Data
  10 +public class SipDeviceDTO implements Serializable {
  11 + @ApiModelProperty(value = "设备国标编号")
  12 + private String cameraCode;
  13 +
  14 + @ApiModelProperty(value = "wan地址")
  15 + private String hostAddress;
  16 +
  17 + @ApiModelProperty(value = "设备访问平台的IP")
  18 + private String localIp;
  19 +
  20 + @ApiModelProperty(value = "传输协议:UDP/TCP")
  21 + private String transport;
  22 +
  23 + @ApiModelProperty(value = "字符集")
  24 + private String charset;
  25 +
  26 + @ApiModelProperty(value = "收流IP")
  27 + private String sdpIp;
  28 +
  29 + @ApiModelProperty(value = "数据流传输模式:UDP / TCP-ACTIVE:tcp主动模式 / TCP-PASSIVE:tcp被动模式")
  30 + private String streamMode;
  31 +
  32 + @ApiModelProperty(value = "通道个数")
  33 + private Integer channelCount;
  34 +
  35 + @ApiModelProperty(value = "设备使用的媒体id, 默认为null")
  36 + private String mediaServerId;
  37 +
  38 + @ApiModelProperty(value = "是否开启ssrc校验,默认关闭,开启可以防止串流")
  39 + private boolean ssrcCheck = true;
  40 + @ApiModelProperty(value = "生产厂商")
  41 + private String manufacturer;
  42 +
  43 + @ApiModelProperty(value = "设备名称")
  44 + private String name;
  45 +
  46 + @ApiModelProperty(value = "设备型号")
  47 + private String model;
  48 +
  49 + @ApiModelProperty(value = "固件版本")
  50 + private String firmware;
  51 + public Integer getStreamModeForParam() {
  52 + if (FastIotConstants.StreamMode.UDP.equalsIgnoreCase(streamMode)) {
  53 + return 0;
  54 + }else if (FastIotConstants.StreamMode.TCP_PASSIVE.equalsIgnoreCase(streamMode)) {
  55 + return 1;
  56 + }else if (FastIotConstants.StreamMode.TCP_ACTIVE.equalsIgnoreCase(streamMode)) {
  57 + return 2;
  58 + }
  59 + return 0;
  60 + }
  61 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Builder;
  4 +import lombok.Data;
  5 +
  6 +import java.io.Serializable;
  7 +
  8 +@Data
  9 +@Builder
  10 +public class SipMessageHeaderDTO implements Serializable {
  11 + private String callId;
  12 + private String viaTag;
  13 + private String fromTag;
  14 + private String toTag;
  15 + private String viaBranch;
  16 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class SsrcInfoDTO {
  7 + /**数据流服务的端口*/
  8 + private int port;
  9 + /**同步源标识符*/
  10 + private String ssrc;
  11 + /**数据流服务ID*/
  12 + private String stream;
  13 +
  14 + public SsrcInfoDTO(int rtpServerPort, String ssrc, String streamId) {
  15 + this.port = rtpServerPort;
  16 + this.ssrc = ssrc;
  17 + stream = streamId;
  18 + }
  19 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.yunteng.enums.SessionTypeEnum;
  5 +
  6 +import java.io.Serializable;
  7 +
  8 +/**
  9 + * 视频流源信息
  10 + */
  11 +@Data
  12 +public class SsrcTransactionDTO implements Serializable {
  13 + private String cameraCode;
  14 + private String channelId;
  15 + private String callId;
  16 + private String stream;
  17 + private String mediaServerId;
  18 + /**SSRC(Source Synchronization Control)是同步源标识符*/
  19 + private String ssrc;
  20 +
  21 + private SipMessageHeaderDTO sipTransactionInfo;
  22 +
  23 + private SessionTypeEnum type;
  24 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 视频流播放地址内容
  8 + */
  9 +@Data
  10 +public class StreamContentDTO {
  11 + @ApiModelProperty(value = "应用名")
  12 + private String app;
  13 +
  14 + @ApiModelProperty(value = "流ID")
  15 + private String stream;
  16 +
  17 + @ApiModelProperty(value = "IP")
  18 + private String ip;
  19 +
  20 + @ApiModelProperty(value = "HTTP-FLV流地址")
  21 + private String flv;
  22 +
  23 + @ApiModelProperty(value = "HTTPS-FLV流地址")
  24 + private String https_flv;
  25 +
  26 + @ApiModelProperty(value = "Websocket-FLV流地址")
  27 + private String ws_flv;
  28 +
  29 + @ApiModelProperty(value = "Websockets-FLV流地址")
  30 + private String wss_flv;
  31 +
  32 + @ApiModelProperty(value = "HTTP-FMP4流地址")
  33 + private String fmp4;
  34 +
  35 + @ApiModelProperty(value = "HTTPS-FMP4流地址")
  36 + private String https_fmp4;
  37 +
  38 + @ApiModelProperty(value = "Websocket-FMP4流地址")
  39 + private String ws_fmp4;
  40 +
  41 + @ApiModelProperty(value = "Websockets-FMP4流地址")
  42 + private String wss_fmp4;
  43 +
  44 + @ApiModelProperty(value = "HLS流地址")
  45 + private String hls;
  46 +
  47 + @ApiModelProperty(value = "HTTPS-HLS流地址")
  48 + private String https_hls;
  49 +
  50 + @ApiModelProperty(value = "Websocket-HLS流地址")
  51 + private String ws_hls;
  52 +
  53 + @ApiModelProperty(value = "Websockets-HLS流地址")
  54 + private String wss_hls;
  55 +
  56 + @ApiModelProperty(value = "HTTP-TS流地址")
  57 + private String ts;
  58 +
  59 + @ApiModelProperty(value = "HTTPS-TS流地址")
  60 + private String https_ts;
  61 +
  62 + @ApiModelProperty(value = "Websocket-TS流地址")
  63 + private String ws_ts;
  64 +
  65 + @ApiModelProperty(value = "Websockets-TS流地址")
  66 + private String wss_ts;
  67 +
  68 + @ApiModelProperty(value = "RTMP流地址")
  69 + private String rtmp;
  70 +
  71 + @ApiModelProperty(value = "RTMPS流地址")
  72 + private String rtmps;
  73 +
  74 + @ApiModelProperty(value = "RTSP流地址")
  75 + private String rtsp;
  76 +
  77 + @ApiModelProperty(value = "RTSPS流地址")
  78 + private String rtsps;
  79 +
  80 + @ApiModelProperty(value = "RTC流地址")
  81 + private String rtc;
  82 +
  83 + @ApiModelProperty(value = "RTCS流地址")
  84 + private String rtcs;
  85 +
  86 + @ApiModelProperty(value = "流媒体ID")
  87 + private String mediaServerId;
  88 +
  89 + @ApiModelProperty(value = "流编码信息")
  90 + private Object tracks;
  91 +
  92 + @ApiModelProperty(value = "开始时间")
  93 + private String startTime;
  94 +
  95 + @ApiModelProperty(value = "结束时间")
  96 + private String endTime;
  97 +
  98 + private double progress;
  99 +
  100 + public StreamContentDTO(StreamInfoDTO streamInfo) {
  101 + if (streamInfo == null) {
  102 + return;
  103 + }
  104 + this.app = streamInfo.getApp();
  105 + this.stream = streamInfo.getStream();
  106 + if (streamInfo.getFlv() != null) {
  107 + this.flv = streamInfo.getFlv().toString();
  108 + }
  109 + if (streamInfo.getHttps_flv() != null) {
  110 + this.https_flv = streamInfo.getHttps_flv().toString();
  111 + }
  112 + if (streamInfo.getWs_flv() != null) {
  113 + this.ws_flv = streamInfo.getWs_flv().toString();
  114 + }
  115 + if (streamInfo.getWss_flv() != null) {
  116 + this.wss_flv = streamInfo.getWss_flv().toString();
  117 + }
  118 + if (streamInfo.getFmp4() != null) {
  119 + this.fmp4 = streamInfo.getFmp4().toString();
  120 + }
  121 + if (streamInfo.getHttps_fmp4() != null) {
  122 + this.https_fmp4 = streamInfo.getHttps_fmp4().toString();
  123 + }
  124 + if (streamInfo.getWs_fmp4() != null) {
  125 + this.ws_fmp4 = streamInfo.getWs_fmp4().toString();
  126 + }
  127 + if (streamInfo.getWss_fmp4() != null) {
  128 + this.wss_fmp4 = streamInfo.getWss_fmp4().toString();
  129 + }
  130 + if (streamInfo.getHls() != null) {
  131 + this.hls = streamInfo.getHls().toString();
  132 + }
  133 + if (streamInfo.getHttps_hls() != null) {
  134 + this.https_hls = streamInfo.getHttps_hls().toString();
  135 + }
  136 + if (streamInfo.getWs_hls() != null) {
  137 + this.ws_hls = streamInfo.getWs_hls().toString();
  138 + }
  139 + if (streamInfo.getWss_hls() != null) {
  140 + this.wss_hls = streamInfo.getWss_hls().toString();
  141 + }
  142 + if (streamInfo.getTs() != null) {
  143 + this.ts = streamInfo.getTs().toString();
  144 + }
  145 + if (streamInfo.getHttps_ts() != null) {
  146 + this.https_ts = streamInfo.getHttps_ts().toString();
  147 + }
  148 + if (streamInfo.getWs_ts() != null) {
  149 + this.ws_ts = streamInfo.getWs_ts().toString();
  150 + }
  151 + if (streamInfo.getRtmp() != null) {
  152 + this.rtmp = streamInfo.getRtmp().toString();
  153 + }
  154 + if (streamInfo.getRtmps() != null) {
  155 + this.rtmps = streamInfo.getRtmps().toString();
  156 + }
  157 + if (streamInfo.getRtsp() != null) {
  158 + this.rtsp = streamInfo.getRtsp().toString();
  159 + }
  160 + if (streamInfo.getRtsps() != null) {
  161 + this.rtsps = streamInfo.getRtsps().toString();
  162 + }
  163 + if (streamInfo.getRtc() != null) {
  164 + this.rtc = streamInfo.getRtc().toString();
  165 + }
  166 + if (streamInfo.getRtcs() != null) {
  167 + this.rtcs = streamInfo.getRtcs().toString();
  168 + }
  169 +
  170 + this.mediaServerId = streamInfo.getMediaServerId();
  171 + this.tracks = streamInfo.getTracks();
  172 + this.startTime = streamInfo.getStartTime();
  173 + this.endTime = streamInfo.getEndTime();
  174 + this.progress = streamInfo.getProgress();
  175 + }
  176 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.io.Serializable;
  7 +import java.util.Objects;
  8 +
  9 +/**
  10 + * 点播的流信息,例如:播放地址
  11 + */
  12 +@Data
  13 +public class StreamInfoDTO implements Serializable, Cloneable {
  14 + @ApiModelProperty(value = "应用名")
  15 + private String app;
  16 +
  17 + @ApiModelProperty(value = "流ID")
  18 + private String stream;
  19 +
  20 + @ApiModelProperty(value = "设备编号")
  21 + private String cameraCode;
  22 +
  23 + @ApiModelProperty(value = "通道编号")
  24 + private String channelId;
  25 +
  26 + @ApiModelProperty(value = "IP")
  27 + private String ip;
  28 +
  29 + @ApiModelProperty(value = "HTTP-FLV流地址")
  30 + private StreamURLDTO flv;
  31 +
  32 + @ApiModelProperty(value = "HTTPS-FLV流地址")
  33 + private StreamURLDTO https_flv;
  34 +
  35 + @ApiModelProperty(value = "Websocket-FLV流地址")
  36 + private StreamURLDTO ws_flv;
  37 +
  38 + @ApiModelProperty(value = "Websockets-FLV流地址")
  39 + private StreamURLDTO wss_flv;
  40 +
  41 + @ApiModelProperty(value = "HTTP-FMP4流地址")
  42 + private StreamURLDTO fmp4;
  43 +
  44 + @ApiModelProperty(value = "HTTPS-FMP4流地址")
  45 + private StreamURLDTO https_fmp4;
  46 +
  47 + @ApiModelProperty(value = "Websocket-FMP4流地址")
  48 + private StreamURLDTO ws_fmp4;
  49 +
  50 + @ApiModelProperty(value = "Websockets-FMP4流地址")
  51 + private StreamURLDTO wss_fmp4;
  52 +
  53 + @ApiModelProperty(value = "HLS流地址")
  54 + private StreamURLDTO hls;
  55 +
  56 + @ApiModelProperty(value = "HTTPS-HLS流地址")
  57 + private StreamURLDTO https_hls;
  58 +
  59 + @ApiModelProperty(value = "Websocket-HLS流地址")
  60 + private StreamURLDTO ws_hls;
  61 +
  62 + @ApiModelProperty(value = "Websockets-HLS流地址")
  63 + private StreamURLDTO wss_hls;
  64 +
  65 + @ApiModelProperty(value = "HTTP-TS流地址")
  66 + private StreamURLDTO ts;
  67 +
  68 + @ApiModelProperty(value = "HTTPS-TS流地址")
  69 + private StreamURLDTO https_ts;
  70 +
  71 + @ApiModelProperty(value = "Websocket-TS流地址")
  72 + private StreamURLDTO ws_ts;
  73 +
  74 + @ApiModelProperty(value = "Websockets-TS流地址")
  75 + private StreamURLDTO wss_ts;
  76 +
  77 + @ApiModelProperty(value = "RTMP流地址")
  78 + private StreamURLDTO rtmp;
  79 +
  80 + @ApiModelProperty(value = "RTMPS流地址")
  81 + private StreamURLDTO rtmps;
  82 +
  83 + @ApiModelProperty(value = "RTSP流地址")
  84 + private StreamURLDTO rtsp;
  85 +
  86 + @ApiModelProperty(value = "RTSPS流地址")
  87 + private StreamURLDTO rtsps;
  88 +
  89 + @ApiModelProperty(value = "RTC流地址")
  90 + private StreamURLDTO rtc;
  91 +
  92 + @ApiModelProperty(value = "RTCS流地址")
  93 + private StreamURLDTO rtcs;
  94 +
  95 + @ApiModelProperty(value = "流媒体ID")
  96 + private String mediaServerId;
  97 +
  98 + @ApiModelProperty(value = "流编码信息")
  99 + private Object tracks;
  100 +
  101 + @ApiModelProperty(value = "开始时间")
  102 + private String startTime;
  103 +
  104 + @ApiModelProperty(value = "结束时间")
  105 + private String endTime;
  106 +
  107 + @ApiModelProperty(value = "进度(录像下载使用)")
  108 + private double progress;
  109 +
  110 + @ApiModelProperty(value = "是否暂停(录像回放使用)")
  111 + private boolean pause;
  112 +
  113 + public void setRtmp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  114 + String file = String.format("%s/%s%s", app, stream, callIdParam);
  115 + if (port > 0) {
  116 + this.rtmp = new StreamURLDTO("rtmp", host, port, file);
  117 + }
  118 + if (sslPort > 0) {
  119 + this.rtmps = new StreamURLDTO("rtmps", host, sslPort, file);
  120 + }
  121 + }
  122 +
  123 + public void setRtsp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  124 + String file = String.format("%s/%s%s", app, stream, callIdParam);
  125 + if (port > 0) {
  126 + this.rtsp = new StreamURLDTO("rtsp", host, port, file);
  127 + }
  128 + if (sslPort > 0) {
  129 + this.rtsps = new StreamURLDTO("rtsps", host, sslPort, file);
  130 + }
  131 + }
  132 +
  133 + public void setFlv(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  134 + String file = String.format("%s/%s.live.flv%s", app, stream, callIdParam);
  135 + if (port > 0) {
  136 + this.flv = new StreamURLDTO("http", host, port, file);
  137 + }
  138 + this.ws_flv = new StreamURLDTO("ws", host, port, file);
  139 + if (sslPort > 0) {
  140 + this.https_flv = new StreamURLDTO("https", host, sslPort, file);
  141 + this.wss_flv = new StreamURLDTO("wss", host, sslPort, file);
  142 + }
  143 + }
  144 +
  145 + public void setFmp4(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  146 + String file = String.format("%s/%s.live.mp4%s", app, stream, callIdParam);
  147 + if (port > 0) {
  148 + this.fmp4 = new StreamURLDTO("http", host, port, file);
  149 + this.ws_fmp4 = new StreamURLDTO("ws", host, port, file);
  150 + }
  151 + if (sslPort > 0) {
  152 + this.https_fmp4 = new StreamURLDTO("https", host, sslPort, file);
  153 + this.wss_fmp4 = new StreamURLDTO("wss", host, sslPort, file);
  154 + }
  155 + }
  156 +
  157 + public void setHls(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  158 + String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam);
  159 + if (port > 0) {
  160 + this.hls = new StreamURLDTO("http", host, port, file);
  161 + this.ws_hls = new StreamURLDTO("ws", host, port, file);
  162 + }
  163 + if (sslPort > 0) {
  164 + this.https_hls = new StreamURLDTO("https", host, sslPort, file);
  165 + this.wss_hls = new StreamURLDTO("wss", host, sslPort, file);
  166 + }
  167 + }
  168 +
  169 + public void setTs(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  170 + String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam);
  171 +
  172 + if (port > 0) {
  173 + this.ts = new StreamURLDTO("http", host, port, file);
  174 + this.ws_ts = new StreamURLDTO("ws", host, port, file);
  175 + }
  176 + if (sslPort > 0) {
  177 + this.https_ts = new StreamURLDTO("https", host, sslPort, file);
  178 + this.wss_ts = new StreamURLDTO("wss", host, sslPort, file);
  179 + }
  180 + }
  181 +
  182 + public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) {
  183 + if (callIdParam != null) {
  184 + callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
  185 + }
  186 + String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam);
  187 + if (port > 0) {
  188 + this.rtc = new StreamURLDTO("http", host, port, file);
  189 + }
  190 + if (sslPort > 0) {
  191 + this.rtcs = new StreamURLDTO("https", host, sslPort, file);
  192 + }
  193 + }
  194 +
  195 + @Override
  196 + public StreamInfoDTO clone() {
  197 + try {
  198 + StreamInfoDTO clone = (StreamInfoDTO) super.clone();
  199 + // TODO: copy mutable state here, so the clone can't change the internals of the original
  200 + return clone;
  201 + } catch (CloneNotSupportedException e) {
  202 + throw new AssertionError();
  203 + }
  204 + }
  205 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import java.io.Serializable;
  8 +
  9 +@ApiModel(value = "流地址信息")
  10 +@Data
  11 +public class StreamURLDTO implements Serializable {
  12 + @ApiModelProperty(value = "协议")
  13 + private String protocol;
  14 +
  15 + @ApiModelProperty(value = "主机地址")
  16 + private String host;
  17 +
  18 + @ApiModelProperty(value = "端口")
  19 + private int port = -1;
  20 +
  21 + @ApiModelProperty(value = "定位位置")
  22 + private String file;
  23 +
  24 + public StreamURLDTO() {}
  25 +
  26 + public StreamURLDTO(String protocol, String host, int port, String file) {
  27 + this.protocol = protocol;
  28 + this.host = host;
  29 + this.port = port;
  30 + this.file = file;
  31 + }
  32 +
  33 + @Override
  34 + public String toString() {
  35 + if (protocol != null && host != null && port != -1) {
  36 + return String.format("%s://%s:%s/%s", protocol, host, port, file);
  37 + } else {
  38 + return null;
  39 + }
  40 + }
  41 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +@ApiModel(value = "摄像机同步状态")
  8 +@Data
  9 +public class SyncStatusDTO {
  10 + @ApiModelProperty(value = "总数")
  11 + private int total;
  12 +
  13 + @ApiModelProperty(value = "当前更新多少")
  14 + private int current;
  15 +
  16 + @ApiModelProperty(value = "错误描述")
  17 + private String errorMsg;
  18 +
  19 + @ApiModelProperty(value = "是否同步中")
  20 + private boolean syncIng;
  21 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import java.time.LocalDateTime;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO;
  9 +import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;
  10 +
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ApiModel(value = "视频通道列表")
  13 +@Data
  14 +public class VideoChanelDTO extends TenantDTO {
  15 + private static final long serialVersionUID = -7234375257329881606L;
  16 +
  17 + @ApiModelProperty(value = "视频通道名称")
  18 + private String name;
  19 +
  20 + @ApiModelProperty(value = "视频通道ID")
  21 + private String channelId;
  22 +
  23 + @ApiModelProperty(value = "设备国标编号")
  24 + private String cameraCode;
  25 +
  26 + @ApiModelProperty(value = "平台设备主键")
  27 + private String deviceId;
  28 +
  29 + @ApiModelProperty(value = "生产厂商")
  30 + private String manufacturer;
  31 +
  32 + @ApiModelProperty(value = "型号")
  33 + private String model;
  34 +
  35 + @ApiModelProperty(value = "设备归属")
  36 + private String owner;
  37 +
  38 + @ApiModelProperty(value = "行政区域")
  39 + private String civilCode;
  40 +
  41 + /** 警区 */
  42 + @ApiModelProperty("警区")
  43 + private String block;
  44 +
  45 + /** 安装地址 */
  46 + @ApiModelProperty("安装地址")
  47 + private String address;
  48 +
  49 + @ApiModelProperty(value = "是否有子设备:0没有 1有")
  50 + private Integer parental;
  51 +
  52 + @ApiModelProperty(value = "信令安全模式缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/\n" + "MIME加密签名同时采用方式;4:数字摘要方式")
  53 + private Integer safetyWay;
  54 +
  55 + @ApiModelProperty(
  56 + value = "注册方式缺省为1;1:符合IETFRFC3261标准的认证注册模\n" + "式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式")
  57 + private Integer registerWay;
  58 +
  59 + @ApiModelProperty(value = "证书序列号")
  60 + private String certNum;
  61 +
  62 + @ApiModelProperty(value = "证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效")
  63 + private Integer certifiable;
  64 +
  65 + @ApiModelProperty(value = "无效原因码")
  66 + private Integer errorCode;
  67 +
  68 + @ApiModelProperty(value = "证书终止有效期")
  69 + private LocalDateTime endTime;
  70 +
  71 + @ApiModelProperty(value = "保密属性缺省为0;0:不涉密,1:涉密")
  72 + private Integer secrecy;
  73 +
  74 + @ApiModelProperty(value = "系统IP地址")
  75 + private String ipAddress;
  76 +
  77 + @ApiModelProperty(value = "端口")
  78 + private Integer port;
  79 +
  80 + @ApiModelProperty(value = "密码")
  81 + private String password;
  82 +
  83 + @ApiModelProperty(value = "通道状态")
  84 + private StatusEnum status;
  85 +
  86 + @ApiModelProperty(value = "云台类型")
  87 + private int PTZType;
  88 +
  89 + @ApiModelProperty(value = "云台类型描述字符串")
  90 + private String PTZTypeText;
  91 +
  92 + @ApiModelProperty(value = "经度")
  93 + private double longitude;
  94 +
  95 + @ApiModelProperty(value = "纬度")
  96 + private double latitude;
  97 +
  98 + @ApiModelProperty(value = "GCJ02坐标系经度")
  99 + private double longitudeGcj02;
  100 +
  101 + @ApiModelProperty(value = "GCJ02坐标系纬度")
  102 + private double latitudeGcj02;
  103 +
  104 + @ApiModelProperty(value = "WGS84坐标系经度")
  105 + private double longitudeWgs84;
  106 +
  107 + @ApiModelProperty(value = "WGS84坐标系纬度")
  108 + private double latitudeWgs84;
  109 +
  110 + @ApiModelProperty(value = "子设备数")
  111 + private int subCount;
  112 +
  113 + @ApiModelProperty(value = "流唯一编号,存在表示正在直播")
  114 + private String streamId;
  115 +
  116 + @ApiModelProperty(value = "是否含有音频")
  117 + private boolean hasAudio;
  118 +
  119 + @ApiModelProperty(value = "标记通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划")
  120 + private int channelType;
  121 +
  122 + public void setPTZType(int PTZType) {
  123 + this.PTZType = PTZType;
  124 + switch (PTZType) {
  125 + case 0:
  126 + this.PTZTypeText = "未知";
  127 + break;
  128 + case 1:
  129 + this.PTZTypeText = "球机";
  130 + break;
  131 + case 2:
  132 + this.PTZTypeText = "半球";
  133 + break;
  134 + case 3:
  135 + this.PTZTypeText = "固定枪机";
  136 + break;
  137 + case 4:
  138 + this.PTZTypeText = "遥控枪机";
  139 + break;
  140 + }
  141 + }
  142 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +public class ZLMServerConfigDTO {}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.cache;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.concurrent.atomic.AtomicInteger;
  7 +
  8 +@Data
  9 +public class CacheMediaServerLoadDTO {
  10 + @ApiModelProperty(value = "流媒体ID")
  11 + private String key;
  12 +
  13 + @ApiModelProperty(value = "流媒体的负载值")
  14 + private AtomicInteger load;
  15 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook;
  2 +
  3 +import java.text.ParseException;
  4 +public interface ChannelOnlineEvent {
  5 + void run(String app, String stream, String serverId) throws ParseException;
  6 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook;
  2 +
  3 +import com.fasterxml.jackson.databind.node.ObjectNode;
  4 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * zlm hook事件的参数
  10 + * @author lin
  11 + */
  12 +public interface IHookSubscribe {
  13 +
  14 + /**
  15 + * 获取hook类型
  16 + * @return hook类型
  17 + */
  18 + HookTypeEnum getHookType();
  19 +
  20 + /**
  21 + * 获取hook的具体内容
  22 + * @return hook的具体内容
  23 + */
  24 + ObjectNode getContent();
  25 +
  26 + /**
  27 + * 设置过期时间
  28 + * @param expiresDateTime 过期时间
  29 + */
  30 + void setExpires(LocalDateTime expiresDateTime);
  31 +
  32 + /**
  33 + * 获取过期时间
  34 + * @return 过期时间
  35 + */
  36 + LocalDateTime getExpires();
  37 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class OtherPsSendInfo {
  7 +
  8 + /** 发流IP */
  9 + private String sendLocalIp;
  10 +
  11 + /** 发流端口 */
  12 + private int sendLocalPort;
  13 +
  14 + /** 收流IP */
  15 + private String receiveIp;
  16 +
  17 + /** 收流端口 */
  18 + private int receivePort;
  19 +
  20 + /** 会话ID */
  21 + private String callId;
  22 +
  23 + /** 流ID */
  24 + private String stream;
  25 +
  26 + /** 推流应用名 */
  27 + private String pushApp;
  28 +
  29 + /** 推流流ID */
  30 + private String pushStream;
  31 +
  32 + /** 推流SSRC */
  33 + private String pushSSRC;
  34 +
  35 + @Override
  36 + public String toString() {
  37 + return "OtherPsSendInfo{"
  38 + + "sendLocalIp='"
  39 + + sendLocalIp
  40 + + '\''
  41 + + ", sendLocalPort="
  42 + + sendLocalPort
  43 + + ", receiveIp='"
  44 + + receiveIp
  45 + + '\''
  46 + + ", receivePort="
  47 + + receivePort
  48 + + ", callId='"
  49 + + callId
  50 + + '\''
  51 + + ", stream='"
  52 + + stream
  53 + + '\''
  54 + + ", pushApp='"
  55 + + pushApp
  56 + + '\''
  57 + + ", pushStream='"
  58 + + pushStream
  59 + + '\''
  60 + + ", pushSSRC='"
  61 + + pushSSRC
  62 + + '\''
  63 + + '}';
  64 + }
  65 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class OtherRtpSendInfo {
  7 +
  8 + /** 发流IP */
  9 + private String sendLocalIp;
  10 +
  11 + /** 音频发流端口 */
  12 + private int sendLocalPortForAudio;
  13 +
  14 + /** 视频发流端口 */
  15 + private int sendLocalPortForVideo;
  16 +
  17 + /** 收流IP */
  18 + private String receiveIp;
  19 +
  20 + /** 音频收流端口 */
  21 + private int receivePortForAudio;
  22 +
  23 + /** 视频收流端口 */
  24 + private int receivePortForVideo;
  25 +
  26 + /** 会话ID */
  27 + private String callId;
  28 +
  29 + /** 流ID */
  30 + private String stream;
  31 +
  32 + /** 推流应用名 */
  33 + private String pushApp;
  34 +
  35 + /** 推流流ID */
  36 + private String pushStream;
  37 +
  38 + /** 推流SSRC */
  39 + private String pushSSRC;
  40 +
  41 + @Override
  42 + public String toString() {
  43 + return "OtherRtpSendInfo{"
  44 + + "sendLocalIp='"
  45 + + sendLocalIp
  46 + + '\''
  47 + + ", sendLocalPortForAudio="
  48 + + sendLocalPortForAudio
  49 + + ", sendLocalPortForVideo="
  50 + + sendLocalPortForVideo
  51 + + ", receiveIp='"
  52 + + receiveIp
  53 + + '\''
  54 + + ", receivePortForAudio="
  55 + + receivePortForAudio
  56 + + ", receivePortForVideo="
  57 + + receivePortForVideo
  58 + + ", callId='"
  59 + + callId
  60 + + '\''
  61 + + ", stream='"
  62 + + stream
  63 + + '\''
  64 + + ", pushApp='"
  65 + + pushApp
  66 + + '\''
  67 + + ", pushStream='"
  68 + + pushStream
  69 + + '\''
  70 + + ", pushSSRC='"
  71 + + pushSSRC
  72 + + '\''
  73 + + '}';
  74 + }
  75 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.BaseParam;
  5 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.OnStreamChangedHookParam;
  6 +
  7 +/** 流的鉴权信息 */
  8 +@Data
  9 +public class StreamAuthorityInfo {
  10 +
  11 + private String id;
  12 + private String app;
  13 + private String stream;
  14 +
  15 + /**
  16 + * 产生源类型, unknown = 0, rtmp_push=1, rtsp_push=2, rtp_push=3, pull=4, ffmpeg_pull=5, mp4_vod=6,
  17 + * device_chn=7
  18 + */
  19 + private int originType;
  20 +
  21 + /** 产生源类型的字符串描述 */
  22 + private String originTypeStr;
  23 +
  24 + /** 推流时自定义的播放鉴权ID */
  25 + private String callId;
  26 +
  27 + /** 推流的鉴权签名 */
  28 + private String sign;
  29 +
  30 + public static StreamAuthorityInfo getInstanceByHook(BaseParam hookParam) {
  31 + StreamAuthorityInfo streamAuthorityInfo = new StreamAuthorityInfo();
  32 + streamAuthorityInfo.setApp(hookParam.getApp());
  33 + streamAuthorityInfo.setStream(hookParam.getStream());
  34 + streamAuthorityInfo.setId(hookParam.getId());
  35 + return streamAuthorityInfo;
  36 + }
  37 +
  38 + public static StreamAuthorityInfo getInstanceByHook(
  39 + OnStreamChangedHookParam onStreamChangedHookParam) {
  40 + StreamAuthorityInfo streamAuthorityInfo = new StreamAuthorityInfo();
  41 + streamAuthorityInfo.setApp(onStreamChangedHookParam.getApp());
  42 + streamAuthorityInfo.setStream(onStreamChangedHookParam.getStream());
  43 + streamAuthorityInfo.setId(onStreamChangedHookParam.getMediaServerId());
  44 + streamAuthorityInfo.setOriginType(onStreamChangedHookParam.getOriginType());
  45 + streamAuthorityInfo.setOriginTypeStr(onStreamChangedHookParam.getOriginTypeStr());
  46 + return streamAuthorityInfo;
  47 + }
  48 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import com.fasterxml.jackson.databind.node.ObjectNode;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.IHookSubscribe;
  6 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Data
  11 +public class BaseHookSubscribe implements IHookSubscribe {
  12 + protected HookTypeEnum hookType;
  13 + protected ObjectNode content;
  14 + protected LocalDateTime expires;
  15 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class BaseParam {
  7 + /** TCP链接唯一ID */
  8 + private String id;
  9 + /** 流应用名 */
  10 + private String app;
  11 + /** 流ID */
  12 + private String stream;
  13 + /** 播放器ip */
  14 + private String ip;
  15 + /** 播放url参数 */
  16 + private String params;
  17 + /** 播放器端口号 */
  18 + private int port;
  19 + /** 播放的协议: rtsp、rtmp、http */
  20 + private String schema;
  21 + /** 流虚拟主机 */
  22 + private String vhost;
  23 + /** 服务器id,通过配置文件设置 */
  24 + private String mediaServerId;
  25 +
  26 + @Override
  27 + public String toString() {
  28 + return String.format("%s://%s:%s/%s/%s?%s", schema, ip, port, app, stream, params);
  29 + }
  30 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * zlm hook事件的参数
  7 + */
  8 +@Data
  9 +public class HookParam {
  10 + /**
  11 + * 服务器id,通过配置文件设置
  12 + */
  13 + private String mediaServerId;
  14 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.StringUtils;
  5 +
  6 +import java.io.Serializable;
  7 +
  8 +@Data
  9 +public class HookResult implements Serializable {
  10 +
  11 + /** 错误代码,0代表允许播放 */
  12 + private int code;
  13 + /** 不允许播放时的错误提示 */
  14 + private String msg;
  15 +
  16 + public HookResult() {}
  17 +
  18 + public HookResult(int code, String msg) {
  19 + this.code = code;
  20 + this.msg = msg;
  21 + }
  22 +
  23 + public static HookResult SUCCESS() {
  24 + return new HookResult(0, "success");
  25 + }
  26 +
  27 + public static HookResult Fail(int code, String message) {
  28 + if (StringUtils.isEmpty(message)) {
  29 + message = "fail";
  30 + }
  31 + return new HookResult(code, message);
  32 + }
  33 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class HookResultForOnPublish extends HookResult {
  7 +
  8 + /** 转协议时是否开启音频 */
  9 + private boolean enableAudio;
  10 + /** 是否允许mp4录制 */
  11 + private boolean enableMp4;
  12 + /** mp4录制切片大小,单位秒 */
  13 + private int mp4MaxSecond;
  14 + /** mp4录制文件保存根目录,置空使用默认 */
  15 + private String mp4SavePath;
  16 +
  17 + public static HookResultForOnPublish SUCCESS() {
  18 + return new HookResultForOnPublish(0, "success");
  19 + }
  20 +
  21 + public HookResultForOnPublish(int code, String msg) {
  22 + setCode(code);
  23 + setMsg(msg);
  24 + }
  25 +
  26 + @Override
  27 + public String toString() {
  28 + return "HookResultForOnPublish{"
  29 + + "enable_audio="
  30 + + enableAudio
  31 + + ", enable_mp4="
  32 + + enableMp4
  33 + + ", mp4_max_second="
  34 + + mp4MaxSecond
  35 + + ", mp4_save_path='"
  36 + + mp4SavePath
  37 + + '\''
  38 + + '}';
  39 + }
  40 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import com.fasterxml.jackson.databind.node.ObjectNode;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  7 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  8 +
  9 +/** hook订阅-收流超时 */
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@Data
  12 +public class HookSubscribeForRtpServerTimeout extends BaseHookSubscribe {
  13 + private HookTypeEnum hookType = HookTypeEnum.ON_RTP_SERVER_TIMEOUT;
  14 + public HookSubscribeForRtpServerTimeout(String stream, String ssrc, String mediaServerId) {
  15 + ObjectNode param = JacksonUtil.newObjectNode();
  16 + param.put("stream_id", stream);
  17 + param.put("ssrc", ssrc);
  18 + param.put("mediaServerId", mediaServerId);
  19 + this.content = param;
  20 + }
  21 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +import lombok.EqualsAndHashCode;
  5 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  6 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  7 +
  8 +/** hook订阅-流变化 */
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@Data
  11 +public class HookSubscribeForServerStarted extends BaseHookSubscribe {
  12 + private HookTypeEnum hookType = HookTypeEnum.ON_SERVER_STARTED;
  13 +
  14 + public HookSubscribeForServerStarted() {
  15 + this.content = JacksonUtil.newObjectNode();
  16 + }
  17 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import com.fasterxml.jackson.databind.node.ObjectNode;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  7 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  8 +
  9 +/**
  10 + * 流改变时
  11 + */
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@Data
  14 +public class HookSubscribeForStreamChange extends BaseHookSubscribe {
  15 +
  16 + private HookTypeEnum hookType = HookTypeEnum.ON_STREAM_CHANGED;
  17 +
  18 + public HookSubscribeForStreamChange(
  19 + String app, String stream, boolean regist, String schema, String mediaServerId) {
  20 + ObjectNode objectNode = JacksonUtil.newObjectNode();
  21 + objectNode.put("app", app);
  22 + objectNode.put("stream", stream);
  23 + objectNode.put("regist", regist);
  24 + if (schema != null) {
  25 + objectNode.put("schema", schema);
  26 + }
  27 + objectNode.put("mediaServerId", mediaServerId);
  28 + this.content = objectNode;
  29 + }
  30 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 调用openRtpServer 接口,rtp server 长时间未收到数据,执行此web hook,对回复不敏感
  7 + */
  8 +@Data
  9 +public class OnRtpServerTimeoutHookParam extends HookParam {
  10 + private int localPort;
  11 + private String streamId;
  12 + private int tcpMode;
  13 + private boolean reUsePort;
  14 + private String ssrc;
  15 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/** zlm hook事件中的on_send_rtp_stopped事件的参数 */
  6 +@Data
  7 +public class OnSendRtpStoppedHookParam extends HookParam {
  8 + /** 流应用名 */
  9 + private String app;
  10 + /** 流ID */
  11 + private String stream;
  12 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +/**
  4 + * zlm hook事件中的on_server_keepalive事件的参数
  5 + */
  6 +public class OnServerKeepaliveHookParam extends HookParam{
  7 +
  8 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +import lombok.EqualsAndHashCode;
  5 +import org.thingsboard.server.common.data.yunteng.dto.sip.StreamContentDTO;
  6 +
  7 +import java.util.List;
  8 +
  9 +/** rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感 */
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@Data
  12 +public class OnStreamChangedHookParam extends HookParam {
  13 +
  14 + /** 注册/注销 */
  15 + private boolean regist;
  16 +
  17 + /** 应用名 */
  18 + private String app;
  19 +
  20 + /** 流id */
  21 + private String stream;
  22 +
  23 + /** 推流鉴权Id */
  24 + private String callId;
  25 +
  26 + /** 观看总人数,包括hls/rtsp/rtmp/http-flv/ws-flv */
  27 + private String totalReaderCount;
  28 +
  29 + /** 协议 包括hls/rtsp/rtmp/http-flv/ws-flv */
  30 + private String schema;
  31 +
  32 + /**
  33 + * 产生源类型, unknown = 0, rtmp_push=1, rtsp_push=2, rtp_push=3, pull=4, ffmpeg_pull=5, mp4_vod=6,
  34 + * device_chn=7
  35 + */
  36 + private int originType;
  37 +
  38 + /** 客户端和服务器网络信息,可能为null类型 */
  39 + private OriginSock originSock;
  40 +
  41 + /** 产生源类型的字符串描述 */
  42 + private String originTypeStr;
  43 +
  44 + /** 产生源的url */
  45 + private String originUrl;
  46 +
  47 + /** 服务器id */
  48 + private String severId;
  49 +
  50 + /** GMT unix系统时间戳,单位秒 */
  51 + private Long createStamp;
  52 +
  53 + /** 存活时间,单位秒 */
  54 + private Long aliveSecond;
  55 +
  56 + /** 数据产生速度,单位byte/s */
  57 + private Long bytesSpeed;
  58 +
  59 + /** 音视频轨道 */
  60 + private List<MediaTrack> tracks;
  61 +
  62 + /** 音视频轨道 */
  63 + private String vhost;
  64 +
  65 + /** 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改 */
  66 + private boolean docker;
  67 +
  68 + @Data
  69 + public static class MediaTrack {
  70 + /** 音频通道数 */
  71 + private int channels;
  72 +
  73 + /** H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4 */
  74 + private int codecId;
  75 +
  76 + /** 编码类型名称 CodecAAC CodecH264 */
  77 + private String codecIdName;
  78 +
  79 + /** Video = 0, Audio = 1 */
  80 + private int codecType;
  81 +
  82 + /** 轨道是否准备就绪 */
  83 + private boolean ready;
  84 +
  85 + /** 音频采样位数 */
  86 + private int sampleBit;
  87 +
  88 + /** 音频采样率 */
  89 + private int sampleRate;
  90 +
  91 + /** 视频fps */
  92 + private int fps;
  93 +
  94 + /** 视频高 */
  95 + private int height;
  96 +
  97 + /** 视频宽 */
  98 + private int width;
  99 + }
  100 +
  101 + @Data
  102 + public static class OriginSock {
  103 + private String identifier;
  104 + private String local_ip;
  105 + private int local_port;
  106 + private String peer_ip;
  107 + private int peer_port;
  108 + }
  109 +
  110 + private StreamContentDTO streamInfo;
  111 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class OnStreamNoneReaderHookParam extends HookParam {
  7 + private String schema;
  8 + private String app;
  9 + private String stream;
  10 + private String vhost;
  11 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip.hook.param;
  2 +
  3 +/**
  4 + * zlm hook事件中的on_stream_not_found事件的参数
  5 + * @author lin
  6 + */
  7 +public class OnStreamNotFoundHookParam extends HookParam{
  8 + private String id;
  9 + private String app;
  10 + private String stream;
  11 + private String ip;
  12 + private String params;
  13 + private int port;
  14 + private String schema;
  15 + private String vhost;
  16 +
  17 +
  18 + public String getId() {
  19 + return id;
  20 + }
  21 +
  22 + public void setId(String id) {
  23 + this.id = id;
  24 + }
  25 +
  26 + public String getApp() {
  27 + return app;
  28 + }
  29 +
  30 + public void setApp(String app) {
  31 + this.app = app;
  32 + }
  33 +
  34 + public String getStream() {
  35 + return stream;
  36 + }
  37 +
  38 + public void setStream(String stream) {
  39 + this.stream = stream;
  40 + }
  41 +
  42 + public String getIp() {
  43 + return ip;
  44 + }
  45 +
  46 + public void setIp(String ip) {
  47 + this.ip = ip;
  48 + }
  49 +
  50 + public String getParams() {
  51 + return params;
  52 + }
  53 +
  54 + public void setParams(String params) {
  55 + this.params = params;
  56 + }
  57 +
  58 + public int getPort() {
  59 + return port;
  60 + }
  61 +
  62 + public void setPort(int port) {
  63 + this.port = port;
  64 + }
  65 +
  66 + public String getSchema() {
  67 + return schema;
  68 + }
  69 +
  70 + public void setSchema(String schema) {
  71 + this.schema = schema;
  72 + }
  73 +
  74 + public String getVhost() {
  75 + return vhost;
  76 + }
  77 +
  78 + public void setVhost(String vhost) {
  79 + this.vhost = vhost;
  80 + }
  81 +
  82 + @Override
  83 + public String toString() {
  84 + return String.format("%s://%s:%s/%s/%s?%s", schema, ip, port, app, stream, params);
  85 + }
  86 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum CatalogDataStatusEnum {
  4 + READY, RUNING, END
  5 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum HookTypeEnum {
  4 + /** 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,阈值通过配置文件 */
  5 + ON_FLOW_REPORT,
  6 + /** 访问http文件服务器上hls之外的文件时触发 */
  7 + ON_HTTP_ACCESS,
  8 + /**
  9 + * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件;
  10 + * 如果流不存在,那么先触发on_play事件然后触发on_stream_not_found事件。
  11 + * 播放rtsp流时,如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件
  12 + */
  13 + ON_PLAY,
  14 + /** rtsp/rtmp/rtp推流鉴权事件 */
  15 + ON_PUBLISH,
  16 + /** 录制mp4完成后通知事件;此事件对回复不敏感 */
  17 + ON_RECORD_MP4,
  18 + /** rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件 */
  19 + ON_RTSP_AUTH,
  20 + /**
  21 + * 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。
  22 + *
  23 + * <p>需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权
  24 + */
  25 + ON_RTSP_REALM,
  26 + /**
  27 + * shell登录鉴权,ZLMediaKit提供简单的telnet调试方式
  28 + *
  29 + * <p>使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面
  30 + */
  31 + ON_SHELL_LOGIN,
  32 + /** rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感 */
  33 + ON_STREAM_CHANGED,
  34 + /**
  35 + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
  36 + * 一个直播流注册上线了,如果一直没人观看也会触发一次无人观看事件,触发时的协议schema是随机的,看哪种协议最晚注册(一般为hls)。
  37 + * 后续从有人观看转为无人观看,触发协议schema为最后一名观看者使用何种协议。
  38 + * 目前mp4/hls录制不当做观看人数(mp4录制可以通过配置文件mp4_as_player控制,但是rtsp/rtmp/rtp转推算观看人数,也会触发该事件
  39 + */
  40 + ON_STREAM_NONE_READER,
  41 + /** 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感 */
  42 + ON_STREAM_NOT_FOUND,
  43 + /** 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感 */
  44 + ON_SERVER_STARTED,
  45 + /** 调用openRtpServer 接口,rtp server 长时间未收到数据,执行此web hook,对回复不敏感 */
  46 + ON_RTP_SERVER_TIMEOUT,
  47 + /** 服务器定时上报时间,上报间隔可配置,默认10s上报一次 */
  48 + ON_SERVER_KEEPALIVE,
  49 + /** 发送rtp(startSendRtp)被动关闭时回调 */
  50 + ON_SEND_RTP_STOPPED
  51 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum InviteStreamTypeEnum {
  4 + PLAY,
  5 + PLAYBACK,
  6 + PUSH,
  7 + PROXY,
  8 + CLOUD_RECORD_PUSH,
  9 + CLOUD_RECORD_PROXY
  10 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum OriginTypeEnum {
  4 + // 不可调整顺序
  5 + UNKNOWN("UNKNOWN"),
  6 + RTMP_PUSH("PUSH"),
  7 + RTSP_PUSH("PUSH"),
  8 + RTP_PUSH("RTP"),
  9 + PULL("PULL"),
  10 + FFMPEG_PULL("PULL"),
  11 + MP4_VOD("MP4_VOD"),
  12 + DEVICE_CHN("DEVICE_CHN"),
  13 + RTC_PUSH("PUSH");
  14 +
  15 + private final String type;
  16 + OriginTypeEnum(String type) {
  17 + this.type = type;
  18 + }
  19 +
  20 + public String getType() {
  21 + return type;
  22 + }
  23 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +import lombok.Getter;
  4 +import lombok.Setter;
  5 +
  6 +/** 云台命令枚举 */
  7 +@Getter
  8 +public enum PTZCommandEnum {
  9 + LEFT("LEFT", 2),
  10 + RIGHT("RIGHT", 1),
  11 + UP("UP", 8),
  12 + DOWN("DOWN", 4),
  13 + UP_LEFT("UP_LEFT", 10),
  14 + UP_RIGHT("UP_RIGHT", 9),
  15 + DOWN_LEFT("DOWN_LEFT", 6),
  16 + DOWN_RIGHT("DOWN_RIGHT", 5),
  17 + ZOOM_IN("ZOOM_IN", 16),
  18 + ZOOM_OUT("ZOOM_OUT", 32),
  19 + STOP("STOP", 0);
  20 +
  21 + private final String command;
  22 + private final Integer cmdCode;
  23 +
  24 + PTZCommandEnum(String command, Integer cmdCode) {
  25 + this.command = command;
  26 + this.cmdCode = cmdCode;
  27 + }
  28 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +import lombok.Getter;
  4 +import lombok.Setter;
  5 +
  6 +/** 云台类型 */
  7 +public enum PTZTypeEnum {
  8 + UNKNOWN("未知", 0),
  9 + BALL("球机", 1),
  10 + HALFSPHERE("半球", 2),
  11 + LOCK_GUNLOCK("固定枪机", 3),
  12 + CONTROLL_GUNLOCK("遥控枪机", 4);
  13 +
  14 + @Getter @Setter private String name;
  15 + @Getter @Setter private Integer index;
  16 +
  17 + PTZTypeEnum(String name, Integer index) {
  18 + this.name = name;
  19 + this.index = index;
  20 + }
  21 +
  22 + public static PTZTypeEnum getItem(Integer index) {
  23 + for (PTZTypeEnum item : values()) {
  24 + if (item.index == index) {
  25 + return item;
  26 + }
  27 + }
  28 + return UNKNOWN;
  29 + }
  30 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum SessionTypeEnum {
  4 + PLAY,
  5 + PLAYBACK,
  6 + DOWNLOAD
  7 +}
... ...
... ... @@ -4,5 +4,6 @@ public enum TransportTypeEnum {
4 4 DEFAULT,
5 5 TCP,
6 6 MQTT,
7   - COAP
  7 + COAP,
  8 + GBT28181
8 9 }
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/** 视频流媒体平台指令类型:cmdType */
  4 +public enum VideoCmdEnum {
  5 + Catalog,
  6 + DeviceControl,
  7 + Alarm,
  8 + Keepalive,
  9 + MediaStatus,
  10 + MobilePosition,
  11 + DeviceInfo,
  12 + DeviceStatus,
  13 + RecordInfo,
  14 + Broadcast,
  15 + ConfigDownload,
  16 + DeviceConfig,
  17 + PresetQuery
  18 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/** 视频流媒体平台接口类型 :method */
  4 +public enum VideoMethodEnum {
  5 + REGISTER,
  6 + MESSAGE,
  7 + BYE,
  8 + CANCEL,
  9 + INVITE,
  10 + NOTIFY,
  11 + SUBSCRIBE,
  12 + INFO,
  13 + ACK
  14 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/** 视频流媒体平台XML数据结构类型:messageType */
  4 +public enum VideoXmlEnum {
  5 + Control,
  6 + Response,
  7 + Notify,
  8 + Query
  9 +}
... ...