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(); | ... | ... |
application/src/main/java/org/thingsboard/server/controller/yunteng/TkVideoChannelController.java
0 → 100644
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 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/yunteng/media/TkVideoControlService.java
0 → 100644
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 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/yunteng/media/ZLMediaKitStateRunner.java
0 → 100644
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}; | ... | ... |
... | ... | @@ -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 | +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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/config/media/MediaConfig.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/config/media/SipConfig.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/config/media/UserSetting.java
0 → 100644
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") | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/TkVideoGptDTO.java
0 → 100644
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 | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/TkVideoGptDeviceDTO.java
0 → 100644
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 | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/CatalogDataDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/MediaServerDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/PTZCmdDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/SendRtpItemDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/SipDeviceDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/SsrcInfoDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/SsrcTransactionDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/StreamContentDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/StreamInfoDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/StreamURLDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/SyncStatusDTO.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/sip/VideoChanelDTO.java
0 → 100644
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.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 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 | +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 | +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 | +/** | |
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/HookTypeEnum.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/OriginTypeEnum.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/PTZCommandEnum.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/PTZTypeEnum.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/VideoCmdEnum.java
0 → 100644
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 | +} | ... | ... |