diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java index 9df8f917..2c830b6d 100644 --- a/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -176,6 +176,7 @@ public interface FastBeeConstant { String RECORDINFO_KEY = "sip:recordinfo:"; String DEVICEID_KEY = "sip:deviceid:"; String STREAM_KEY = "sip:stream:"; + String INVITE_KEY = "sip:invite:"; String SIP_CSEQ_PREFIX = "sip:CSEQ:"; String DEFAULT_SIP_CONFIG = "sip:config"; String DEFAULT_MEDIA_CONFIG = "sip:mediaconfig"; diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java index 32659b0c..63cae605 100644 --- a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java @@ -66,6 +66,14 @@ public class RedisKeyBuilder { return FastBeeConstant.REDIS.STREAM_KEY + steamId; } + public static String buildStreamCacheKey(String deviceId, String channelId, String stream, String ssrc){ + return FastBeeConstant.REDIS.STREAM_KEY + deviceId + ":" + channelId + ":" + stream + ":" + ssrc; + } + + public static String buildInviteCacheKey(String type, String deviceId, String channelId, String stream, String ssrc){ + return FastBeeConstant.REDIS.INVITE_KEY + type + ":"+ deviceId + ":" + channelId + ":" + stream + ":" + ssrc; + } + /**ipCSEQ缓存key*/ public static String buildSipCSEQCacheKey(String CSEQ){ return FastBeeConstant.REDIS.SIP_CSEQ_PREFIX + CSEQ; diff --git a/springboot/fastbee-open-api/pom.xml b/springboot/fastbee-open-api/pom.xml index 6e04ab1f..549f17c8 100644 --- a/springboot/fastbee-open-api/pom.xml +++ b/springboot/fastbee-open-api/pom.xml @@ -17,8 +17,12 @@ com.fastbee mqtt-broker + + com.fastbee + sip-server + - + diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/MediaServerController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/MediaServerController.java new file mode 100644 index 00000000..ca32d247 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/MediaServerController.java @@ -0,0 +1,149 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.service.IMediaServerService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; + +/** + * 流媒体服务器配置Controller + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Api(tags = "流媒体服务器配置") +@RestController +@RequestMapping("/sip/mediaserver") +public class MediaServerController extends BaseController +{ + @Autowired + private IMediaServerService mediaServerService; + + /** + * 查询流媒体服务器配置列表 + */ + @ApiOperation("查询流媒体服务器配置列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/list") + public TableDataInfo list(MediaServer mediaServer) + { + startPage(); + List list = mediaServerService.selectMediaServerList(mediaServer); + return getDataTable(list); + } + + /** + * 导出流媒体服务器配置列表 + */ + @ApiOperation("导出流媒体服务器配置列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @Log(title = "流媒体服务器配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MediaServer mediaServer) + { + List list = mediaServerService.selectMediaServerList(mediaServer); + ExcelUtil util = new ExcelUtil(MediaServer.class); + util.exportExcel(response, list, "流媒体服务器配置数据"); + } + + /** + * 获取流媒体服务器配置详细信息,只获取第一条 + */ + @ApiOperation(value = "获取流媒体服务器配置详细信息", notes = "只获取第一条") + @PreAuthorize("@ss.hasPermi('iot:video:query')") + @GetMapping() + public AjaxResult getInfo() + { + List list=mediaServerService.selectMediaServer(); + if(list==null || list.size()==0){ + MediaServer mediaServer=new MediaServer(); + // 设置默认值 + mediaServer.setEnabled(1); + mediaServer.setDomain(""); + mediaServer.setIp(""); + mediaServer.setPortHttp(8082L); + mediaServer.setPortHttps(8443L); + mediaServer.setPortRtmp(1935L); + mediaServer.setPortRtsp(554L); + mediaServer.setProtocol("HTTP"); + mediaServer.setSecret("035c73f7-bb6b-4889-a715-d9eb2d192xxx"); + mediaServer.setRtpPortRange("30000,30500"); + list=new ArrayList<>(); + list.add(mediaServer); + } + return AjaxResult.success(list.get(0)); + } + + /** + * 新增流媒体服务器配置 + */ + @ApiOperation("新增流媒体服务器配置") + @PreAuthorize("@ss.hasPermi('iot:video:add')") + @Log(title = "流媒体服务器配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody MediaServer mediaServer) + { + return toAjax(mediaServerService.insertMediaServer(mediaServer)); + } + + /** + * 修改流媒体服务器配置 + */ + @ApiOperation("修改流媒体服务器配置") + @PreAuthorize("@ss.hasPermi('iot:video:edit')") + @Log(title = "流媒体服务器配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MediaServer mediaServer) + { + return toAjax(mediaServerService.updateMediaServer(mediaServer)); + } + + /** + * 删除流媒体服务器配置 + */ + @ApiOperation("删除流媒体服务器配置") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "流媒体服务器配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(mediaServerService.deleteMediaServerByIds(ids)); + } + + @ApiOperation("获取流媒体服务器视频流信息列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/mediaList/{schema}/{stream}") + public AjaxResult getMediaList(@PathVariable String schema, + @PathVariable String stream) + { + return AjaxResult.success("success!", mediaServerService.getMediaList(schema,stream)); + } + + @ApiOperation("获取rtp推流端口列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/listRtpServer") + public AjaxResult listRtpServer() + { + return AjaxResult.success("success!", mediaServerService.listRtpServer()); + } + + @ApiOperation("检验流媒体服务") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping(value = "/check") + public AjaxResult checkMediaServer(@RequestParam String ip, @RequestParam Long port, @RequestParam String secret) { + return AjaxResult.success("success!", mediaServerService.checkMediaServer(ip, port, secret)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PlayerController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PlayerController.java new file mode 100644 index 00000000..06310c39 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PlayerController.java @@ -0,0 +1,62 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.sip.service.IPlayService; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/sip/player") +public class PlayerController extends BaseController { + @Autowired + private IPlayService playService; + + @GetMapping("/getBigScreenUrl/{deviceId}/{channelId}") + public AjaxResult getBigScreenUrl(@PathVariable String deviceId, @PathVariable String channelId) { + return AjaxResult.success("success!", playService.play(deviceId, channelId,false).getHttps_fmp4()); + } + + @ApiOperation("直播播放") + @GetMapping("/play/{deviceId}/{channelId}") + public AjaxResult play(@PathVariable String deviceId, @PathVariable String channelId) { + return AjaxResult.success("success!", playService.play(deviceId, channelId,false)); + } + @ApiOperation("回放播放") + @GetMapping("/playback/{deviceId}/{channelId}") + public AjaxResult playback(@PathVariable String deviceId, + @PathVariable String channelId, String start, String end) { + return AjaxResult.success("success!", playService.playback(deviceId, channelId, start, end)); + } + @ApiOperation("停止推流") + @GetMapping("/closeStream/{deviceId}/{channelId}/{streamId}") + public AjaxResult closeStream(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) { + return AjaxResult.success("success!", playService.closeStream(deviceId, channelId, streamId)); + } + @ApiOperation("回放暂停") + @GetMapping("/playbackPause/{deviceId}/{channelId}/{streamId}") + public AjaxResult playbackPause(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) { + return AjaxResult.success("success!", playService.playbackPause(deviceId, channelId, streamId)); + } + @ApiOperation("回放恢复") + @GetMapping("/playbackReplay/{deviceId}/{channelId}/{streamId}") + public AjaxResult playbackReplay(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) { + return AjaxResult.success("success!", playService.playbackReplay(deviceId, channelId, streamId)); + } + @ApiOperation("录像回放定位") + @GetMapping("/playbackSeek/{deviceId}/{channelId}/{streamId}") + public AjaxResult playbackSeek(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId, long seek) { + return AjaxResult.success("success!", playService.playbackSeek(deviceId, channelId, streamId, seek)); + } + @ApiOperation("录像倍速播放") + @GetMapping("/playbackSpeed/{deviceId}/{channelId}/{streamId}") + public AjaxResult playbackSpeed(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId, Integer speed) { + return AjaxResult.success("success!", playService.playbackSpeed(deviceId, channelId, streamId, speed)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PtzController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PtzController.java new file mode 100644 index 00000000..d611703c --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/PtzController.java @@ -0,0 +1,73 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.sip.enums.Direct; +import com.fastbee.sip.model.PtzDirectionInput; +import com.fastbee.sip.model.PtzscaleInput; +import com.fastbee.sip.service.IPtzCmdService; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/sip/ptz") +public class PtzController { + @Autowired + private IPtzCmdService ptzCmdService; + @ApiOperation("云台方向控制") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @PostMapping("/direction/{deviceId}/{channelId}") + public AjaxResult direction(@PathVariable String deviceId, @PathVariable String channelId, @RequestBody PtzDirectionInput ptzDirectionInput) { + Direct direct = null; + int leftRight = ptzDirectionInput.getLeftRight(); + int upDown = ptzDirectionInput.getUpDown(); + if (leftRight == 1) { + direct = Direct.RIGHT; + } else if (leftRight == 2) { + direct = Direct.LEFT; + } else if (upDown == 1) { + direct = Direct.UP; + } else if (upDown == 2) { + direct = Direct.DOWN; + } else { + direct = Direct.STOP; + } + Integer speed = ptzDirectionInput.getMoveSpeed(); + return AjaxResult.success("success!", ptzCmdService.directPtzCmd(deviceId, channelId, direct, speed)); + } + @ApiOperation("云台缩放控制") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @PostMapping("/scale/{deviceId}/{channelId}") + public AjaxResult scale(@PathVariable String deviceId, @PathVariable String channelId, @RequestBody PtzscaleInput ptzscaleInput) { + Direct direct = null; + if (ptzscaleInput.getInOut() == 1) { + direct = Direct.ZOOM_IN; + } else if (ptzscaleInput.getInOut() == 2) { + direct = Direct.ZOOM_OUT; + } else { + direct = Direct.STOP; + } + Integer speed = ptzscaleInput.getScaleSpeed(); + return AjaxResult.success("success!", ptzCmdService.directPtzCmd(deviceId, channelId, direct, speed)); + } + @ApiOperation("云台ptz控制") + @PostMapping("/ptz/{deviceId}/{channelId}/{direct}/{speed}") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + public AjaxResult ptzControl(@PathVariable String deviceId, + @PathVariable String channelId, + @PathVariable Direct direct, + @PathVariable Integer speed) { + return AjaxResult.success("success!", ptzCmdService.directPtzCmd(deviceId, channelId, direct, speed)); + } + @ApiOperation("云台停止控制") + @PostMapping("/ptz/{deviceId}/{channelId}/STOP") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + public AjaxResult ptzControlStop(@PathVariable String deviceId, + @PathVariable String channelId) { + return AjaxResult.success("success!", ptzCmdService.directPtzCmd(deviceId, channelId, Direct.STOP, 0)); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/RecordController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/RecordController.java new file mode 100644 index 00000000..ba5586a4 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/RecordController.java @@ -0,0 +1,155 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.service.IRecordService; +import com.fastbee.sip.util.WebAsyncUtil; +import com.fastbee.sip.util.result.BaseResult; +import com.fastbee.sip.util.result.CodeEnum; +import com.fastbee.sip.util.result.DataResult; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.async.WebAsyncTask; + +@Slf4j +@RestController +@RequestMapping("/sip/record") +public class RecordController extends BaseController { + + @Autowired + private IRecordService recordService; + + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + @ApiOperation("设备录像查询") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/devquery/{deviceId}/{channelId}") + public WebAsyncTask devquery(@PathVariable String deviceId, + @PathVariable String channelId, String start, String end) { + return WebAsyncUtil.init(taskExecutor, () -> { + try { + RecordList result = recordService.listDevRecord(deviceId, channelId, start, end); + return DataResult.out(CodeEnum.SUCCESS, result); + } catch (Exception e) { + log.error("", e); + return BaseResult.out(CodeEnum.FAIL, e.getMessage()); + } + }); + } + @ApiOperation("设备录像缓存查询") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/query/{channelId}/{sn}") + public AjaxResult list(@PathVariable String channelId, + @PathVariable String sn) { + return AjaxResult.success("success!", recordService.listRecord(channelId, sn)); + } + + @ApiOperation("指定流ID开始录像") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/start/{stream}") + public AjaxResult startRecord(@PathVariable String stream) { + boolean result = recordService.startRecord(stream); + if (result) { + return AjaxResult.success("success!"); + } else { + return AjaxResult.error("error!"); + } + } + + @ApiOperation("指定流ID停止录像") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/stop/{stream}") + public AjaxResult stopRecord(@PathVariable String stream) { + boolean result = recordService.stopRecord(stream); + if (result) { + return AjaxResult.success("success!"); + } else { + return AjaxResult.error("error!"); + } + } + + @ApiOperation("获取流对应的录像文件列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/file/{stream}/{period}") + public AjaxResult getMp4RecordFile(@PathVariable String stream, + @PathVariable String period) { + return AjaxResult.success("success!", recordService.getMp4RecordFile(stream, period)); + } + + @ApiOperation("直播录像") + @GetMapping("/play/{deviceId}/{channelId}") + public AjaxResult playRecord(@PathVariable String deviceId, @PathVariable String channelId) { + logger.debug(String.format("直播录像 API调用,deviceId:%s,channelId:%s", deviceId, channelId)); + return AjaxResult.success("success!", recordService.playRecord(deviceId, channelId)); + } + + @PreAuthorize("@ss.hasPermi('iot:sip:record:download')") + @ApiOperation("设备录像下载") + @GetMapping("/download/{deviceId}/{channelId}") + public AjaxResult download(@PathVariable String deviceId, @PathVariable String channelId, + String startTime, String endTime, String speed) { + logger.debug(String.format("设备录像下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, speed)); + return AjaxResult.success("success!", recordService.download(deviceId, channelId, startTime, endTime, Integer.parseInt(speed))); + } + + + + @ApiOperation("查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/list") + public AjaxResult listServerRecord(@RequestParam Integer pageNum, + @RequestParam Integer pageSize, @RequestParam String recordApi) { + try{ + Object data = recordService.listServerRecord(recordApi, pageNum, pageSize); + return AjaxResult.success("success!", data); + }catch(Exception e){ + return AjaxResult.error("连接超时或发生错误,未获取到数据!"); + } + } + @ApiOperation("通过日期查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/date/list") + public AjaxResult listServerRecordByDate(@RequestParam(required = false) Integer year, + @RequestParam(required = false) Integer month, @RequestParam String app, @RequestParam String stream, @RequestParam String recordApi) { + return AjaxResult.success("success!", recordService.listServerRecordByDate(recordApi, year, month, app, stream)); + } + @ApiOperation("通过流ID查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/stream/list") + public AjaxResult listServerRecordByStream(@RequestParam Integer pageNum, + @RequestParam Integer pageSize, @RequestParam String app, @RequestParam String recordApi) { + return AjaxResult.success("success!", recordService.listServerRecordByStream(recordApi, pageNum, pageSize, app)); + } + @ApiOperation("通过应用名查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/app/list") + public AjaxResult listServerRecordByApp(@RequestParam Integer pageNum, + @RequestParam Integer pageSize, @RequestParam String recordApi) { + return AjaxResult.success("success!", recordService.listServerRecordByApp(recordApi, pageNum, pageSize)); + } + @ApiOperation("通过文件名查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/file/list") + public AjaxResult listServerRecordByFile(@RequestParam Integer pageNum, @RequestParam Integer pageSize, + @RequestParam String app, @RequestParam String stream, + @RequestParam String startTime, @RequestParam String endTime, @RequestParam String recordApi) { + return AjaxResult.success("success!", recordService.listServerRecordByFile(recordApi, pageNum, pageSize, app, stream, startTime, endTime)); + } + + @ApiOperation("通过设备信息查询服务端录像列表") + @PreAuthorize("@ss.hasPermi('iot:sip:record:list')") + @GetMapping("/serverRecord/device/list") + public AjaxResult getServerRecordByDevice(@RequestParam Integer pageNum, @RequestParam Integer pageSize, + @RequestParam String deviceId, @RequestParam String channelId, + @RequestParam String startTime, @RequestParam String endTime) { + return AjaxResult.success("success!", recordService.listServerRecordByDevice(pageNum, pageSize, deviceId, channelId, startTime, endTime)); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipConfigController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipConfigController.java new file mode 100644 index 00000000..e558a213 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipConfigController.java @@ -0,0 +1,81 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.service.ISipConfigService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * sip系统配置Controller + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Api(tags = "sip系统配置") +@RestController +@RequestMapping("/sip/sipconfig") +public class SipConfigController extends BaseController { + @Autowired + private ISipConfigService sipConfigService; + + /** + * 获取产品下第一条sip系统配置详细信息 + */ + @ApiOperation("获取产品下sip系统配置信息") + @PreAuthorize("@ss.hasPermi('iot:video:query')") + @GetMapping(value = "/{productId}/{isDefault}") + public AjaxResult getInfo(@PathVariable("productId") Long productId, @PathVariable("isDefault") Boolean isDefault) { + SipConfig sipConfig = isDefault ? sipConfigService.GetDefaultSipConfig() : sipConfigService.selectSipConfigByProductId(productId); + return AjaxResult.success(sipConfig); + } + + /** + * 新增sip系统配置 + */ + @ApiOperation("新增sip系统配置") + @PreAuthorize("@ss.hasPermi('iot:video:add')") + @Log(title = "sip系统配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SipConfig sipConfig) { + return AjaxResult.success(sipConfigService.insertSipConfig(sipConfig)); + } + + /** + * 修改sip系统配置 + */ + @ApiOperation("修改sip系统配置") + @PreAuthorize("@ss.hasPermi('iot:video:edit')") + @Log(title = "sip系统配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SipConfig sipConfig) { + return toAjax(sipConfigService.updateSipConfig(sipConfig)); + } + + /** + * 删除sip系统配置 + */ + @ApiOperation("删除sip系统配置") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "sip系统配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(sipConfigService.deleteSipConfigByIds(ids)); + } + + @ApiOperation("批量删除sip系统配置") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "sip系统配置", businessType = BusinessType.DELETE) + @DeleteMapping("/product/{productIds}") + public AjaxResult removeByProductId(@PathVariable Long[] productIds) { + // 设备可能不存在通道,可以返回0 + int result = sipConfigService.deleteSipConfigByProductIds(productIds); + return AjaxResult.success(result); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceChannelController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceChannelController.java new file mode 100644 index 00000000..4f5e314b --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceChannelController.java @@ -0,0 +1,106 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.service.ISipDeviceChannelService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 监控设备通道信息Controller + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Api(tags = "监控设备通道信息") +@RestController +@RequestMapping("/sip/channel") +public class SipDeviceChannelController extends BaseController +{ + @Autowired + private ISipDeviceChannelService sipDeviceChannelService; + + /** + * 查询监控设备通道信息列表 + */ + @ApiOperation("查询监控设备通道信息列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/list") + public TableDataInfo list(SipDeviceChannel sipDeviceChannel) + { + startPage(); + List list = sipDeviceChannelService.selectSipDeviceChannelList(sipDeviceChannel); + return getDataTable(list); + } + + /** + * 导出监控设备通道信息列表 + */ + @ApiOperation("导出监控设备通道信息列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @Log(title = "监控设备通道信息", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SipDeviceChannel sipDeviceChannel) + { + List list = sipDeviceChannelService.selectSipDeviceChannelList(sipDeviceChannel); + ExcelUtil util = new ExcelUtil(SipDeviceChannel.class); + util.exportExcel(response, list, "监控设备通道信息数据"); + } + + /** + * 获取监控设备通道信息详细信息 + */ + @ApiOperation("获取监控设备通道信息详细信息") + @PreAuthorize("@ss.hasPermi('iot:video:query')") + @GetMapping(value = "/{channelId}") + public AjaxResult getInfo(@PathVariable("channelId") Long channelId) + { + return AjaxResult.success(sipDeviceChannelService.selectSipDeviceChannelByChannelId(channelId)); + } + + /** + * 新增监控设备通道信息 + */ + @ApiOperation("新增监控设备通道信息") + @PreAuthorize("@ss.hasPermi('iot:video:add')") + @Log(title = "监控设备通道信息", businessType = BusinessType.INSERT) + @PostMapping(value = "/{createNum}") + public AjaxResult add(@PathVariable("createNum") Long createNum, @RequestBody SipDeviceChannel sipDeviceChannel) { + return AjaxResult.success("操作成功", sipDeviceChannelService.insertSipDeviceChannelGen(createNum, sipDeviceChannel)); + } + + /** + * 修改监控设备通道信息 + */ + @ApiOperation("修改监控设备通道信息") + @PreAuthorize("@ss.hasPermi('iot:video:edit')") + @Log(title = "监控设备通道信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SipDeviceChannel sipDeviceChannel) + { + return toAjax(sipDeviceChannelService.updateSipDeviceChannel(sipDeviceChannel)); + } + + /** + * 删除监控设备通道信息 + */ + @ApiOperation("删除监控设备通道信息") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "监控设备通道信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{channelIds}") + public AjaxResult remove(@PathVariable Long[] channelIds) + { + return toAjax(sipDeviceChannelService.deleteSipDeviceChannelByChannelIds(channelIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceController.java new file mode 100644 index 00000000..34d67187 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/SipDeviceController.java @@ -0,0 +1,144 @@ +package com.fastbee.data.controller.media; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.service.ISipDeviceService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.compress.utils.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.List; + +/** + * 监控设备Controller + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Api(tags = "监控设备") +@RestController +@RequestMapping("/sip/device") +public class SipDeviceController extends BaseController +{ + @Autowired + private ISipDeviceService sipDeviceService; + + /** + * 查询监控设备列表 + */ + @ApiOperation("查询监控设备列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/list") + public TableDataInfo list(SipDevice sipDevice) + { + startPage(); + List list = sipDeviceService.selectSipDeviceList(sipDevice); + return getDataTable(list); + } + + @ApiOperation("查询监控设备树结构") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @GetMapping("/listchannel/{deviceId}") + public AjaxResult listchannel(@PathVariable("deviceId") String deviceId) + { + return AjaxResult.success(sipDeviceService.selectSipDeviceChannelList(deviceId)); + } + + /** + * 导出监控设备列表 + */ + @ApiOperation("导出监控设备列表") + @PreAuthorize("@ss.hasPermi('iot:video:list')") + @Log(title = "监控设备", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SipDevice sipDevice) + { + List list = sipDeviceService.selectSipDeviceList(sipDevice); + ExcelUtil util = new ExcelUtil(SipDevice.class); + util.exportExcel(response, list, "监控设备数据"); + } + + /** + * 获取监控设备详细信息 + */ + @ApiOperation("获取监控设备详细信息") + @PreAuthorize("@ss.hasPermi('iot:video:query')") + @GetMapping(value = "/{deviceId}") + public AjaxResult getInfo(@PathVariable("deviceId") String deviceId) + { + return AjaxResult.success(sipDeviceService.selectSipDeviceBySipId(deviceId)); + } + + /** + * 新增监控设备 + */ + @ApiOperation("新增监控设备") + @PreAuthorize("@ss.hasPermi('iot:video:add')") + @Log(title = "监控设备", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SipDevice sipDevice) + { + return toAjax(sipDeviceService.insertSipDevice(sipDevice)); + } + + /** + * 修改监控设备 + */ + @ApiOperation("修改监控设备") + @PreAuthorize("@ss.hasPermi('iot:video:edit')") + @Log(title = "监控设备", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SipDevice sipDevice) + { + return toAjax(sipDeviceService.updateSipDevice(sipDevice)); + } + + /** + * 删除监控设备 + */ + @ApiOperation("根据设备id批量删除监控设备") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "监控设备", businessType = BusinessType.DELETE) + @DeleteMapping("/{deviceIds}") + public AjaxResult remove(@PathVariable Long[] deviceIds) + { + return toAjax(sipDeviceService.deleteSipDeviceByDeviceIds(deviceIds)); + } + + @ApiOperation("根据sipId删除") + @PreAuthorize("@ss.hasPermi('iot:video:remove')") + @Log(title = "监控设备", businessType = BusinessType.DELETE) + @DeleteMapping("/sipid/{sipId}") + public AjaxResult remove(@PathVariable String sipId) + { + return toAjax(sipDeviceService.deleteSipDeviceBySipId(sipId)); + } + + @ApiOperation("根据设备id捕捉") + @PreAuthorize("@ss.hasPermi('iot:video:query')") + @GetMapping("/snap/{deviceId}/{channelId}") + public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) { + try { + final InputStream in = Files.newInputStream(new File(FileUploadUtils.getDefaultBaseDir() + File.separator + "snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath()); + resp.setContentType(MediaType.IMAGE_PNG_VALUE); + IOUtils.copy(in, resp.getOutputStream()); + } catch (IOException e) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/ZmlHookController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/ZmlHookController.java new file mode 100644 index 00000000..9ecd05f6 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/media/ZmlHookController.java @@ -0,0 +1,105 @@ +package com.fastbee.data.controller.media; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.service.IZmlHookService; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/zlmhook") +public class ZmlHookController +{ + + @Autowired + private IZmlHookService zmlHookService; + @ApiOperation("访问流媒体服务器上的文件时触发回调") + @ResponseBody + @PostMapping(value = "/on_http_access", produces = "application/json;charset=UTF-8") + public ResponseEntity onHttpAccess(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onHttpAccess(json).toString(), HttpStatus.OK); + } + @ApiOperation("播放器鉴权事件回调") + @ResponseBody + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") + public ResponseEntity onPlay(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onPlay(json).toString(), HttpStatus.OK); + } + @ApiOperation("rtsp/rtmp/rtp推流鉴权事件回调") + @ResponseBody + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") + public ResponseEntity onPublish(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onPublish(json).toString(),HttpStatus.OK); + } + @ApiOperation("流无人观看时事件回调") + @ResponseBody + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") + public ResponseEntity onStreamNoneReader(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onStreamNoneReader(json).toString(),HttpStatus.OK); + } + @ApiOperation("流未找到事件回调") + @ResponseBody + @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") + public ResponseEntity onStreamNotFound(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onStreamNotFound(json).toString(),HttpStatus.OK); + } + @ApiOperation("流注册或注销时触发此事件回调") + @ResponseBody + @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") + public ResponseEntity onStreamChanged(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onStreamChanged(json).toString(),HttpStatus.OK); + } + @ApiOperation("流量统计事件回调") + @ResponseBody + @PostMapping(value = "/on_flow_report", produces = "application/json;charset=UTF-8") + public ResponseEntity onFlowReport(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onFlowReport(json).toString(),HttpStatus.OK); + } + @ApiOperation("rtp服务器长时间未收到数据超时回调") + @ResponseBody + @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") + public ResponseEntity onRtpServerTimeout(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onRtpServerTimeout(json).toString(),HttpStatus.OK); + } + + @ApiOperation("rtp发送停止回调") + @ResponseBody + @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") + public ResponseEntity onSendRtpStopped(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onSendRtpStopped(json).toString(),HttpStatus.OK); + } + + @ApiOperation("录制mp4完成后通知事件回调") + @ResponseBody + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") + public ResponseEntity onRecordMp4(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onRecordMp4(json).toString(),HttpStatus.OK); + } + + @ApiOperation("流媒体服务器启动回调") + @ResponseBody + @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") + public ResponseEntity onServerStarted(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onServerStarted(json).toString(),HttpStatus.OK); + } + + @ApiOperation("流媒体服务器心跳回调") + @ResponseBody + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") + public ResponseEntity onServerKeepalive(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onServerKeepalive(json).toString(),HttpStatus.OK); + } + + @ApiOperation("流媒体服务器存活回调") + @ResponseBody + @PostMapping(value = "/on_server_exited", produces = "application/json;charset=UTF-8") + public ResponseEntity onServerExited(@RequestBody JSONObject json){ + return new ResponseEntity(zmlHookService.onServerExited(json).toString(),HttpStatus.OK); + } + + +} diff --git a/springboot/fastbee-server/sip-server/README.md b/springboot/fastbee-server/sip-server/README.md new file mode 100644 index 00000000..ef333004 --- /dev/null +++ b/springboot/fastbee-server/sip-server/README.md @@ -0,0 +1,29 @@ + +## GB28181 Roadmap +- 设备基础信息上报 +- 物模型适配(属性,功能) +- 设备直播 +- 设备录像拉取 +- PTZ云台控制 +- 设备自动拉流 云端录像 +- tcp传输信令 +- 添加事件处理机制 +- srs m7s流媒体服务器支持 +- 百度evs,七牛云qvs等云平台api对接 + +## webhook +- on_http_access +- on_play +- on_publish +- on_stream_none_reader //StopPlay +- on_stream_not_found +- on_stream_changed + +## zlm api +- url + "/index/api/getRtpInfo?secret=" + secret + "&stream_id=" + ssrc 获取流在zlm上的信息 +- url + "/index/api/openRtpServer?secret=" + secret + "&port=0&enable_tcp=1&stream_id=" + streamId + "&ssrc=" + ssrc 创建GB28181 RTP接收端口 +- url + "/index/api/closeRtpServer?secret=" + secret + "&stream_id=" + streamId 关闭GB28181 RTP接收端口 +- url + "/index/api/listRtpServer?secret=" + secret 获取RTP服务器 +- url + "/index/api/close_streams?secret=" + secret + "&stream=" + ssrc 关闭流 +- url + "/index/api/getMediaList?secret=" + secret + "&stream=" + ssrc 获取流信息 + diff --git a/springboot/fastbee-server/sip-server/pom.xml b/springboot/fastbee-server/sip-server/pom.xml new file mode 100644 index 00000000..2db90390 --- /dev/null +++ b/springboot/fastbee-server/sip-server/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + fastbee-server + com.fastbee + 3.8.5 + + + sip-server + GB28181信令服务 + + + + + org.projectlombok + lombok + + + + + org.apache.httpcomponents + httpclient + + + + + com.alibaba + transmittable-thread-local + 2.14.0 + + + + javax.sip + jain-sip-ri + 1.3.0-91 + + + + org.dom4j + dom4j + 2.1.3 + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + + org.apache.log4j + com.springsource.org.apache.log4j + 1.2.16 + + + + com.squareup.okhttp3 + okhttp + + + + com.fasterxml.jackson.core + jackson-databind + + + + com.fastbee + fastbee-mq + + + + + diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/SysSipConfig.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/SysSipConfig.java new file mode 100644 index 00000000..a95528ad --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/SysSipConfig.java @@ -0,0 +1,20 @@ +package com.fastbee.sip.conf; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + + +@Setter +@Getter +@Component +@ConfigurationProperties(prefix = "sip") +public class SysSipConfig { + boolean enabled; + String ip; + Integer port; + String domain; + String id; + String password; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/ThreadPoolTaskConfig.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/ThreadPoolTaskConfig.java new file mode 100644 index 00000000..d5c14fdd --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/conf/ThreadPoolTaskConfig.java @@ -0,0 +1,32 @@ +package com.fastbee.sip.conf; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableAsync(proxyTargetClass = true) +public class ThreadPoolTaskConfig { + public static final int cpuNum = Runtime.getRuntime().availableProcessors(); + private static final int corePoolSize = cpuNum; + private static final int maxPoolSize = cpuNum*2; + private static final int keepAliveTime = 30; + private static final int queueCapacity = 10000; + private static final String threadNamePrefix = "sip-"; + + @Bean("taskExecutor") + public ThreadPoolTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveTime); + executor.setThreadNamePrefix(threadNamePrefix); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/MediaServer.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/MediaServer.java new file mode 100644 index 00000000..f6ef90af --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/MediaServer.java @@ -0,0 +1,120 @@ +package com.fastbee.sip.domain; + +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 流媒体服务器配置对象 media_server + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@ApiModel(value = "MediaServer", description = "流媒体服务器配置对象 media_server") +@Data +public class MediaServer extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 流媒体配置ID */ + @ApiModelProperty("流媒体配置ID") + private Long id; + + @ApiModelProperty("服务器标识") + private String serverId; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @ApiModelProperty(value = "是否系统通用", notes = "(0-否,1-是)") + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 使能开关 */ + @ApiModelProperty("使能开关") + @Excel(name = "使能开关") + private Integer enabled; + + /** 默认播放协议 */ + @ApiModelProperty("默认播放协议") + @Excel(name = "默认播放协议") + private String protocol; + + @ApiModelProperty("回调服务器地址") + @Excel(name = "回调服务器地址") + private String hookurl; + + /** 服务器ip */ + @ApiModelProperty("服务器ip") + @Excel(name = "服务器ip") + private String ip; + + /** 服务器域名 */ + @ApiModelProperty("服务器域名") + @Excel(name = "服务器域名") + private String domain; + + /** 流媒体密钥 */ + @ApiModelProperty("流媒体密钥") + @Excel(name = "流媒体密钥") + private String secret; + + /** http端口 */ + @ApiModelProperty("http端口") + @Excel(name = "http端口") + private Long portHttp; + + /** ws端口 */ + @ApiModelProperty("ws端口") + @Excel(name = "HTTPS端口") + private Long portHttps; + + /** rtmp端口 */ + @ApiModelProperty("rtmp端口") + @Excel(name = "rtmp端口") + private Long portRtmp; + + /** rtsp端口 */ + @ApiModelProperty("rtsp端口") + @Excel(name = "rtsp端口") + private Long portRtsp; + + @ApiModelProperty("RTP收流端口") + @Excel(name = "RTP收流端口(单端口模式有用)") + private Long rtpProxyPort; + + @ApiModelProperty("是否使用多端口模式") + @Excel(name = "是否使用多端口模式") + private Integer rtpEnable; + + /** rtp端口范围 */ + @ApiModelProperty("rtp端口范围") + @Excel(name = "rtp端口范围") + private String rtpPortRange; + + @ApiModelProperty("是否自动同步配置ZLM") + @Excel(name = "是否自动同步配置ZLM") + private Integer autoConfig; + + @ApiModelProperty("状态") + @Excel(name = "状态") + private Integer status; + + @ApiModelProperty("录像服务端口") + @Excel(name = "录像服务端口") + private Long recordPort; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipConfig.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipConfig.java new file mode 100644 index 00000000..1fb3522d --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipConfig.java @@ -0,0 +1,95 @@ +package com.fastbee.sip.domain; + +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * sip系统配置对象 sip_config + * + * @author zhuangpeng.li + * @date 2023-02-21 + */ +@ApiModel(value = "SipConfig", description = "sip系统配置对象 sip_config") +@Data +public class SipConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 配置ID */ + @ApiModelProperty("配置ID") + private Long id; + + /** 是否系统通用(0-否,1-是) */ + @ApiModelProperty(value = "是否系统通用", notes = "(0-否,1-是)") + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 产品ID */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** + * 产品名称 + */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** + * 使能开关 + */ + @ApiModelProperty("使能开关") + @Excel(name = "使能开关") + private Integer enabled; + + /** + * 系统默认配置 + */ + @ApiModelProperty("系统默认配置") + @Excel(name = "系统默认配置") + private Integer isdefault; + + /** + * 拓展sdp + */ + @ApiModelProperty("拓展sdp") + @Excel(name = "拓展sdp") + private Integer seniorsdp; + + /** + * 服务器域 + */ + @ApiModelProperty("服务器域") + @Excel(name = "服务器域") + private String domain; + + /** + * 服务器sipid + */ + @ApiModelProperty("服务器sipid") + @Excel(name = "服务器sipid") + private String serverSipid; + + /** sip认证密码 */ + @ApiModelProperty("sip认证密码") + @Excel(name = "sip认证密码") + private String password; + + /** sip接入IP */ + @ApiModelProperty("sip接入IP") + @Excel(name = "sip接入IP") + private String ip; + + /** sip接入端口号 */ + @ApiModelProperty("sip接入端口号") + @Excel(name = "sip接入端口号") + private Integer port; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDevice.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDevice.java new file mode 100644 index 00000000..d185c171 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDevice.java @@ -0,0 +1,310 @@ +package com.fastbee.sip.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.Date; + +/** + * 监控设备对象 sip_device + * + * @author zhuangpeng.li + * @date 2023-02-24 + */ +@ApiModel(value = "SipDevice", description = "监控设备对象 sip_device") +public class SipDevice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 设备ID */ + @ApiModelProperty("设备ID") + private Long deviceId; + + /** 产品ID */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** 设备SipID */ + @ApiModelProperty("设备SipID") + @Excel(name = "设备SipID") + private String deviceSipId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 厂商名称 */ + @ApiModelProperty("厂商名称") + @Excel(name = "厂商名称") + private String manufacturer; + + /** 产品型号 */ + @ApiModelProperty("产品型号") + @Excel(name = "产品型号") + private String model; + + /** 固件版本 */ + @ApiModelProperty("固件版本") + @Excel(name = "固件版本") + private String firmware; + + /** 传输模式 */ + @ApiModelProperty("传输模式") + @Excel(name = "传输模式") + private String transport; + + /** 流模式 */ + @ApiModelProperty("流模式") + @Excel(name = "流模式") + private String streammode; + + /** 在线状态 */ + @ApiModelProperty("在线状态") + @Excel(name = "在线状态") + private String online; + + /** 注册时间 */ + @ApiModelProperty("注册时间") + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "注册时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date registertime; + + /** 最后上线时间 */ + @ApiModelProperty("最后上线时间") + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "最后上线时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date lastconnecttime; + + /** 激活时间 */ + @ApiModelProperty("激活时间") + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "激活时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date activeTime; + + /** 设备入网IP */ + @ApiModelProperty("设备入网IP") + @Excel(name = "设备入网IP") + private String ip; + + /** 设备接入端口号 */ + @ApiModelProperty("设备接入端口号") + @Excel(name = "设备接入端口号") + private Integer port; + + /** 设备地址 */ + @ApiModelProperty("设备地址") + @Excel(name = "设备地址") + private String hostaddress; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setDeviceSipId(String deviceSipId) + { + this.deviceSipId = deviceSipId; + } + + public String getDeviceSipId() + { + return deviceSipId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setManufacturer(String manufacturer) + { + this.manufacturer = manufacturer; + } + + public String getManufacturer() + { + return manufacturer; + } + public void setModel(String model) + { + this.model = model; + } + + public String getModel() + { + return model; + } + public void setFirmware(String firmware) + { + this.firmware = firmware; + } + + public String getFirmware() + { + return firmware; + } + public void setTransport(String transport) + { + this.transport = transport; + } + + public String getTransport() + { + return transport; + } + public void setStreammode(String streammode) + { + this.streammode = streammode; + } + + public String getStreammode() + { + return streammode; + } + public void setOnline(String online) + { + this.online = online; + } + + public String getOnline() + { + return online; + } + public void setRegistertime(Date registertime) + { + this.registertime = registertime; + } + + public Date getRegistertime() + { + return registertime; + } + public void setLastconnecttime(Date lastconnecttime) + { + this.lastconnecttime = lastconnecttime; + } + + public Date getLastconnecttime() + { + return lastconnecttime; + } + public void setActiveTime(Date activeTime) + { + this.activeTime = activeTime; + } + + public Date getActiveTime() + { + return activeTime; + } + public void setIp(String ip) + { + this.ip = ip; + } + + public String getIp() + { + return ip; + } + public void setPort(Integer port) + { + this.port = port; + } + + public Integer getPort() + { + return port; + } + public void setHostaddress(String hostaddress) + { + this.hostaddress = hostaddress; + } + + public String getHostaddress() + { + return hostaddress; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deviceId", getDeviceId()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("deviceSipId", getDeviceSipId()) + .append("deviceName", getDeviceName()) + .append("manufacturer", getManufacturer()) + .append("model", getModel()) + .append("firmware", getFirmware()) + .append("transport", getTransport()) + .append("streammode", getStreammode()) + .append("online", getOnline()) + .append("registertime", getRegistertime()) + .append("lastconnecttime", getLastconnecttime()) + .append("activeTime", getActiveTime()) + .append("ip", getIp()) + .append("port", getPort()) + .append("hostaddress", getHostaddress()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } + + public String getHostAndPort() { + return getIp() + ":" + getPort(); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDeviceChannel.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDeviceChannel.java new file mode 100644 index 00000000..4380acb1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/domain/SipDeviceChannel.java @@ -0,0 +1,270 @@ +package com.fastbee.sip.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 监控设备通道信息对象 sip_device_channel + * + * @author zhuangpeng.li + * @date 2023-02-23 + */ +@ApiModel(value = "SipDeviceChannel", description = "监控设备通道信息对象 sip_device_channel") +@Data +public class SipDeviceChannel extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @ApiModelProperty("ID") + private Long id; + + /** + * 租户ID + */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** + * 租户名称 + */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** + * 产品ID + */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** + * 产品名称 + */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** + * 产品ID + */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long userId; + + /** + * 产品名称 + */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String userName; + + /** + * 设备SipID + */ + @ApiModelProperty("设备SipID") + private String deviceSipId; + + /** + * 通道SipID + */ + @ApiModelProperty("通道SipID") + @Excel(name = "通道SipID") + private String channelSipId; + + /** + * 通道名称 + */ + @ApiModelProperty("通道名称") + @Excel(name = "通道名称") + private String channelName; + + /** + * 注册时间 + */ + @ApiModelProperty("注册时间") + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "注册时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date registerTime; + + /** + * 设备类型 + */ + @ApiModelProperty("设备类型") + @Excel(name = "设备类型") + private String deviceType; + + /** + * 通道类型 + */ + @ApiModelProperty("通道类型") + @Excel(name = "通道类型") + private String channelType; + + /** + * 城市编码 + */ + @ApiModelProperty("城市编码") + @Excel(name = "城市编码") + private String citycode; + + /** + * 行政区域 + */ + @ApiModelProperty("行政区域") + @Excel(name = "行政区域") + private String civilcode; + + /** + * 厂商名称 + */ + @ApiModelProperty("厂商名称") + @Excel(name = "厂商名称") + private String manufacture; + + /** + * 产品型号 + */ + @ApiModelProperty("产品型号") + @Excel(name = "产品型号") + private String model; + + /** + * 设备归属 + */ + @ApiModelProperty("设备归属") + @Excel(name = "设备归属") + private String owner; + + /** + * 警区 + */ + @ApiModelProperty("警区") + @Excel(name = "警区") + private String block; + + /** + * 安装地址 + */ + @ApiModelProperty("安装地址") + @Excel(name = "安装地址") + private String address; + + /** 父级id */ + @ApiModelProperty("父级id") + @Excel(name = "父级id") + private String parentid; + + /** 设备入网IP */ + @ApiModelProperty("设备入网IP") + @Excel(name = "设备入网IP") + private String ipaddress; + + /** 设备接入端口号 */ + @ApiModelProperty("设备接入端口号") + @Excel(name = "设备接入端口号") + private Integer port; + + /** 密码 */ + @ApiModelProperty("密码") + @Excel(name = "密码") + private String password; + + /** PTZ类型 */ + @ApiModelProperty("PTZ类型") + @Excel(name = "PTZ类型") + private Long ptztype; + + /** PTZ类型描述字符串 */ + @ApiModelProperty("PTZ类型描述字符串") + @Excel(name = "PTZ类型描述字符串") + private String ptztypetext; + + /** + * 设备状态(1=-未使用,2-在线,3-离线,4-禁用) + */ + @ApiModelProperty("设备状态(1=-未使用,2-在线,3-离线,4-禁用)") + @Excel(name = "设备状态", readConverterExp = "1=-未使用,2-在线,3-离线,4-禁用") + private Integer status; + /** + * 推流状态(1-未使用,2-录像中) + */ + @ApiModelProperty("推流状态(1-未使用,2-录像中)") + @Excel(name = "推流状态", readConverterExp = "1-未使用,2-录像中") + private Integer streamPush; + /** + * 直播录像状态(1=-未使用,2-录像中) + */ + @ApiModelProperty("直播录像状态(1-未使用,2-录像中)") + @Excel(name = "直播录像状态", readConverterExp = "1-未使用,2-录像中") + private Integer streamRecord; + + /** + * 录像转存状态(1-未使用,2-录像中) + */ + @ApiModelProperty("录像转存状态(1-未使用,2-录像中)") + @Excel(name = "录像转存状态", readConverterExp = "1-未使用,2-录像中") + private Integer videoRecord; + + /** + * 设备经度 + */ + @ApiModelProperty("设备经度") + @Excel(name = "设备经度") + private BigDecimal longitude; + + /** + * 设备纬度 + */ + @ApiModelProperty("设备纬度") + @Excel(name = "设备纬度") + private BigDecimal latitude; + + /** + * 流媒体ID + */ + @ApiModelProperty("流媒体ID") + @Excel(name = "流媒体ID") + private String streamid; + + /** + * 子设备数 + */ + @ApiModelProperty("子设备数") + @Excel(name = "子设备数") + private Long subcount; + + /** + * 是否有子设备(1-有, 0-没有) + */ + @ApiModelProperty("是否有子设备(1-有, 0-没有)") + @Excel(name = "是否有子设备", readConverterExp = "1=-有,,0=-没有") + private Integer parental; + + /** + * 是否含有音频(1-有, 0-没有) + */ + @ApiModelProperty("是否含有音频(1-有, 0-没有)") + @Excel(name = "是否含有音频", readConverterExp = "1=-有,,0=-没有") + private Integer hasaudio; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + +} \ No newline at end of file diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmMethod.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmMethod.java new file mode 100644 index 00000000..7fc7552c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmMethod.java @@ -0,0 +1,20 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum AlarmMethod { + unknown("0", "未知"), + telAlarm("1", "电话报警"), + devAlarm("2", "设备报警"), + smsAlarm("3", "短信报警"), + gpsAlarm("4", "GPS报警"), + videoAlarm("5", "视频报警"), + devErrorAlarm("6", "设备故障报警"), + other("7", "其他报警"); + private final String value; + + private final String text; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmType.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmType.java new file mode 100644 index 00000000..90939b7e --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/AlarmType.java @@ -0,0 +1,61 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@AllArgsConstructor +@Getter +public enum AlarmType { + //设备报警 + videoLost("1", "视频丢失报警", AlarmMethod.devAlarm), + videoBroken("2", "设备防拆报警", AlarmMethod.devAlarm), + diskFull("3", "存储设备磁盘满报警", AlarmMethod.devAlarm), + hot("4", "设备高温报警", AlarmMethod.devAlarm), + cold("5", "设备低温报警", AlarmMethod.devAlarm), + //视频报警 + manual("1", "人工视频报警", AlarmMethod.videoAlarm), + moving("2", "运动目标检测报警", AlarmMethod.videoAlarm), + residual("3", "遗留物检测报警", AlarmMethod.videoAlarm), + remove("4", "物体移除检测报警", AlarmMethod.videoAlarm), + tripLine("5", "绊线检测报警", AlarmMethod.videoAlarm), + intrusion("6", "入侵检测报警", AlarmMethod.videoAlarm), + retrograde("7", "逆行检测报警", AlarmMethod.videoAlarm), + wandering("8", "徘徊检测报警", AlarmMethod.videoAlarm), + density("9", "密度检测报警", AlarmMethod.videoAlarm), + error("10", "视频异常检测报警", AlarmMethod.videoAlarm), + fastMoving("11", "快速移动报警", AlarmMethod.videoAlarm), + + //存储设备 + storageDiskError("1", "存储设备磁盘故障报警", AlarmMethod.devErrorAlarm), + + storageFanError("2", "存储设备风扇故障报警", AlarmMethod.devErrorAlarm); + + private final String code; + + private final String text; + + private final AlarmMethod method; + + private static final Map> fastMap = new EnumMap<>(AlarmMethod.class); + + static { + for (AlarmType value : AlarmType.values()) { + fastMap.computeIfAbsent(value.method, (ignore) -> new HashMap<>()) + .put(value.code, value); + } + } + + public static Optional of(AlarmMethod method, String code) { + Map mapping = fastMap.get(method); + if (mapping == null) { + return Optional.empty(); + } + return Optional.ofNullable(mapping.get(code)); + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelStatus.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelStatus.java new file mode 100644 index 00000000..4e17352b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelStatus.java @@ -0,0 +1,22 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ChannelStatus { + online("ON", "在线"), + + lost("VLOST", "视频丢失"), + defect("DEFECT", "故障"), + add("ADD", "新增"), + delete("DEL", "删除"), + update("UPDATE", "更新"), + offline("OFF", "离线"), + ; + + private final String code; + private final String text; + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelType.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelType.java new file mode 100644 index 00000000..5baf0bc5 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/ChannelType.java @@ -0,0 +1,5 @@ +package com.fastbee.sip.enums; + +public enum ChannelType{ + CivilCode, BusinessGroup,VirtualOrganization,Other +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/DeviceChannelStatus.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/DeviceChannelStatus.java new file mode 100644 index 00000000..97af2b23 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/DeviceChannelStatus.java @@ -0,0 +1,15 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum DeviceChannelStatus { + //1=-未使用,2-在线,3-离线,4-禁用 + notused(1), + online(2), + offline(3), + off(4); + private final Integer value; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/Direct.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/Direct.java new file mode 100644 index 00000000..2019102f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/Direct.java @@ -0,0 +1,51 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; + +import java.util.Arrays; + +@AllArgsConstructor +public enum Direct { + UP(0x08), + DOWN(0x04), + LEFT(0x02), + RIGHT(0x01), + ZOOM_IN(0x10), + ZOOM_OUT(0x20), + STOP(0) { + @Override + public int merge(int code) { + return code; + } + + @Override + public boolean match(int code) { + return code == 0; + } + }; + private final int code; + + public int merge(int code) { + return code | this.code; + } + + public boolean match(int code) { + return (code & this.code) != 0; + } + + public static Direct[] values(int code) { + return Arrays + .stream(values()) + .filter(direct -> direct.match(code)) + .toArray(Direct[]::new); + } + + public static Direct[] values(String code) { + String[] codes = code.toUpperCase().split(","); + + return Arrays + .stream(codes) + .map(Direct::valueOf) + .toArray(Direct[]::new); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/FunctionType.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/FunctionType.java new file mode 100644 index 00000000..e4e081bb --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/FunctionType.java @@ -0,0 +1,25 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +@AllArgsConstructor +@Getter +public enum FunctionType { + VIDEOPUSH("video_push", "设备推流"), + AUDIOBROADCAST("audio_broadcast", "语音广播"), + OTHER("other", "其他"); + private final String value; + private final String text; + public static FunctionType fromType(String Type) { + for (FunctionType type : FunctionType.values()) { + if (Objects.equals(type.getValue(), Type)) { + return type; + } + } + return null; + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZCmd.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZCmd.java new file mode 100644 index 00000000..1c5e9032 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZCmd.java @@ -0,0 +1,17 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PTZCmd { + out(32), + in(16), + up(8), + down(4), + left(2), + right(1); + + private final Integer value; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZType.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZType.java new file mode 100644 index 00000000..f2fded1f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/PTZType.java @@ -0,0 +1,17 @@ +package com.fastbee.sip.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PTZType { + unknown(0, "未知"), + ball(1, "球机"), + hemisphere(2, "半球机"), + fixed(3, "固定抢机"), + remoteControl(4, "遥控抢机"); + + private final Integer value; + private final String text; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/SessionType.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/SessionType.java new file mode 100644 index 00000000..c9894c45 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/enums/SessionType.java @@ -0,0 +1,8 @@ +package com.fastbee.sip.enums; + +public enum SessionType { + play, + playrecord, + playback, + download +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IReqHandler.java new file mode 100644 index 00000000..9da2e491 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IReqHandler.java @@ -0,0 +1,7 @@ +package com.fastbee.sip.handler; + +import javax.sip.RequestEvent; + +public interface IReqHandler { + public void processMsg(RequestEvent evt); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IResHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IResHandler.java new file mode 100644 index 00000000..baf95dca --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/IResHandler.java @@ -0,0 +1,8 @@ +package com.fastbee.sip.handler; + +import javax.sip.ResponseEvent; +import java.text.ParseException; + +public interface IResHandler { + public void processMsg(ResponseEvent evt) throws ParseException; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/AckReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/AckReqHandler.java new file mode 100644 index 00000000..c5d662a4 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/AckReqHandler.java @@ -0,0 +1,36 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.text.ParseException; + +@Slf4j +@Component +public class AckReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(RequestEvent evt) { + log.info("接收到ACK信息"); + try { + responseAck(evt); + } catch (SipException | InvalidArgumentException | ParseException e) { + log.error("[回复ACK信息失败],{}", e.getMessage()); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "ACK"; + sipListener.addRequestProcessor(method, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ByeReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ByeReqHandler.java new file mode 100644 index 00000000..1c0c3f8a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ByeReqHandler.java @@ -0,0 +1,38 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.server.IGBListener; +import gov.nist.javax.sip.message.SIPRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; + +@Slf4j +@Component +public class ByeReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(RequestEvent evt) { + log.info("接收到BYE信息"); + try { + responseAck(evt); + } catch (SipException | InvalidArgumentException | ParseException e) { + log.error("[回复BYE信息失败],{}", e.getMessage()); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "BYE"; + sipListener.addRequestProcessor(method, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/CancelReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/CancelReqHandler.java new file mode 100644 index 00000000..91a56de7 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/CancelReqHandler.java @@ -0,0 +1,36 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.text.ParseException; + +@Slf4j +@Component +public class CancelReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(RequestEvent evt) { + log.info("接收到CANCEL信息"); + try { + responseAck(evt); + } catch (SipException | InvalidArgumentException | ParseException e) { + log.error("[回复CANCEL信息失败],{}", e.getMessage()); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "CANCEL"; + sipListener.addRequestProcessor(method, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/InviteReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/InviteReqHandler.java new file mode 100644 index 00000000..7b263709 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/InviteReqHandler.java @@ -0,0 +1,36 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.text.ParseException; + +@Slf4j +@Component +public class InviteReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(RequestEvent evt) { + log.info("接收到INVITE信息"); + try { + responseAck(evt); + } catch (SipException | InvalidArgumentException | ParseException e) { + log.error("[回复INVITE信息失败],{}", e.getMessage()); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "INVITE"; + sipListener.addRequestProcessor(method, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/RegisterReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/RegisterReqHandler.java new file mode 100644 index 00000000..b9ad500a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/RegisterReqHandler.java @@ -0,0 +1,194 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.sip.conf.SysSipConfig; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.model.SipDate; +import com.fastbee.sip.server.IGBListener; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.service.ISipConfigService; +import com.fastbee.sip.service.ISipDeviceChannelService; +import com.fastbee.sip.service.ISipDeviceService; +import com.fastbee.sip.util.DigestAuthUtil; +import gov.nist.javax.sip.address.AddressImpl; +import gov.nist.javax.sip.address.SipUri; +import gov.nist.javax.sip.header.Expires; +import gov.nist.javax.sip.header.SIPDateHeader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.*; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; + +@Slf4j +@Component +public class RegisterReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler { + + @Autowired + private ISipDeviceService sipDeviceService; + @Autowired + private ISipDeviceChannelService sipDeviceChannelService; + @Autowired + private IDeviceService deviceService; + + @Autowired + private ISipConfigService sipConfigService; + + @Autowired + private IGBListener sipListener; + + @Autowired + private MessageInvoker messageInvoker; + + @Autowired + private SysSipConfig sysSipConfig; + + @Override + public void processMsg(RequestEvent evt) { + try { + log.info("收到注册请求,开始处理"); + Request request = evt.getRequest(); + Response response; + // 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功 + int registerFlag; + FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); + AddressImpl address = (AddressImpl) fromHeader.getAddress(); + SipUri uri = (SipUri) address.getURI(); + String sipId = uri.getUser(); + //取默认Sip配置 + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(sipId); + if (sipConfig != null) { + AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + // 校验密码是否正确 + if (authorhead == null && !ObjectUtils.isEmpty(sipConfig.getPassword())) { + log.info("未携带授权头 回复401,sipId:"+ sipId); + response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); + new DigestAuthUtil().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); + getServerTransaction(evt).sendResponse(response); + return; + } + + boolean pcheck = new DigestAuthUtil().doAuthenticatePlainTextPassword(request, + sipConfig.getPassword()); + + boolean syscheck = new DigestAuthUtil().doAuthenticatePlainTextPassword(request, + sysSipConfig.getPassword()); + if (!pcheck && !syscheck) { + // 注册失败 + response = getMessageFactory().createResponse(Response.FORBIDDEN, request); + response.setReasonPhrase("wrong password"); + log.info("[注册请求] 密码/SIP服务器ID错误, 回复403 sipId:" + sipId); + getServerTransaction(evt).sendResponse(response); + return; + } + response = getMessageFactory().createResponse(Response.OK, request); + // 添加date头 + SIPDateHeader dateHeader = new SIPDateHeader(); + // 使用自己修改的 + SipDate sipDate = new SipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis()); + dateHeader.setDate(sipDate); + response.addHeader(dateHeader); + + ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); + // 添加Contact头 + response.addHeader(request.getHeader(ContactHeader.NAME)); + // 添加Expires头 + response.addHeader(request.getExpires()); + ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + String received = viaHeader.getReceived(); + int rPort = viaHeader.getRPort(); + // 本地模拟设备 received 为空 rPort 为 -1 + // 解析本地地址替代 + if (StringUtils.isEmpty(received) || rPort == -1) { + log.warn("本地地址替代! received:{},rPort:{} [{}:{}]", received, rPort, viaHeader.getHost(), viaHeader.getPort()); + received = viaHeader.getHost(); + rPort = viaHeader.getPort(); + } + SipDevice device = new SipDevice(); + ; + device.setStreammode("UDP"); + device.setDeviceSipId(sipId); + device.setIp(received); + device.setPort(rPort); + device.setHostaddress(received.concat(":").concat(String.valueOf(rPort))); + // 注销成功 + if (expiresHeader != null && expiresHeader.getExpires() == 0) { + registerFlag = 2; + } else { + registerFlag = 1; + // 判断TCP还是UDP + boolean isTcp = false; + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + String transport = reqViaHeader.getTransport(); + if (transport.equals("TCP")) { + isTcp = true; + } + device.setTransport(isTcp ? "TCP" : "UDP"); + } + getServerTransaction(evt).sendResponse(response); + // 注册成功 + if (registerFlag == 1) { + log.info("注册成功! sipId:" + device.getDeviceSipId()); + device.setRegistertime(DateUtils.getNowDate()); + sipDeviceService.updateDevice(device); + List channels = sipDeviceChannelService.selectSipDeviceChannelByDeviceSipId(device.getDeviceSipId()); + if (channels.size() > 0) { + Device iotdev = deviceService.selectDeviceBySerialNumber(device.getDeviceSipId()); + if (iotdev == null) { + //添加到统一设备管理 默认添加到admin账号下 + int result = deviceService.insertDeviceAuto(device.getDeviceSipId(), 1L, sipConfig.getProductId()); + if (result == 1) { + log.info("-----------sip设备认证成功,并自动添加设备到系统,SipId:" + device.getDeviceSipId() + "---------------"); + } + iotdev = new Device(); + } + if (iotdev.getStatus() != 3 && iotdev.getStatus() != 2) { + iotdev.setStatus(3); + deviceService.updateDeviceStatusAndLocation(iotdev,device.getIp()); + } + // 重新注册更新设备和通道,以免设备替换或更新后信息无法更新 + onRegister(device); + } else { + log.info("未找到改设备! deviceId:" + device.getDeviceId()); + } + } else if (registerFlag == 2) { + log.info("注销成功! deviceId:" + device.getDeviceId()); + } + + } + } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) { + e.printStackTrace(); + } + } + + public void onRegister(SipDevice device) { + // TODO 查询设备信息和下挂设备信息 自动拉流 + messageInvoker.deviceInfoQuery(device); + messageInvoker.catalogQuery(device); + messageInvoker.subscribe(device,0,0,3600,"0",null,null); + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "REGISTER"; + sipListener.addRequestProcessor(method, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ReqAbstractHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ReqAbstractHandler.java new file mode 100644 index 00000000..55d6ebb6 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/ReqAbstractHandler.java @@ -0,0 +1,162 @@ +package com.fastbee.sip.handler.req; + +import gov.nist.javax.sip.SipStackImpl; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; +import gov.nist.javax.sip.stack.SIPServerTransaction; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import javax.sip.*; +import javax.sip.header.FromHeader; +import javax.sip.header.HeaderFactory; +import javax.sip.header.ToHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.MessageFactory; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Slf4j +public abstract class ReqAbstractHandler { + @Autowired + @Qualifier(value = "udpSipServer") + private SipProvider udpSipServer; + + public ServerTransaction getServerTransaction(RequestEvent evt) { + Request request = evt.getRequest(); + ServerTransaction serverTransaction = evt.getServerTransaction(); + // 判断TCP还是UDP + boolean isTcp = false; + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + String transport = reqViaHeader.getTransport(); + if (transport.equals("TCP")) { + isTcp = true; + } + + if (serverTransaction == null) { + try { + if (!isTcp) { + SipStackImpl stack = (SipStackImpl)udpSipServer.getSipStack(); + serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true); + if (serverTransaction == null) { + serverTransaction = udpSipServer.getNewServerTransaction(request); + } + } + } catch (TransactionAlreadyExistsException | TransactionUnavailableException e) { + e.printStackTrace(); + } + } + return serverTransaction; + } + + public HeaderFactory getHeaderFactory() { + try { + return SipFactory.getInstance().createHeaderFactory(); + } catch (PeerUnavailableException e) { + e.printStackTrace(); + } + return null; + } + + public MessageFactory getMessageFactory() { + try { + return SipFactory.getInstance().createMessageFactory(); + } catch (PeerUnavailableException e) { + e.printStackTrace(); + } + return null; + } + + public void responseAck(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException { + Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest()); + getServerTransaction(evt).sendResponse(response); + } + + public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode) throws SipException, InvalidArgumentException, ParseException { + return responseAck(serverTransaction, statusCode, null); + } + + public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException { + ToHeader toHeader = (ToHeader) serverTransaction.getRequest().getHeader(ToHeader.NAME); + if (toHeader.getTag() == null) { + toHeader.setTag(String.valueOf(System.currentTimeMillis())); + } + SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, serverTransaction.getRequest()); + if (msg != null) { + response.setReasonPhrase(msg); + } + serverTransaction.sendResponse(response); + if (statusCode >= 200 && !"NOTIFY".equalsIgnoreCase(serverTransaction.getRequest().getMethod())) { + if (serverTransaction.getDialog() != null) { + serverTransaction.getDialog().delete(); + } + } + return response; + } + + public Element getRootElement(RequestEvent evt) throws DocumentException { + return getRootElement(evt, "gb2312"); + } + public Element getRootElement(RequestEvent evt, String charset) throws DocumentException { + if (charset == null) { + charset = "gb2312"; + } + Request request = evt.getRequest(); + SAXReader reader = new SAXReader(); + reader.setEncoding(charset); + // 对海康出现的未转义字符做处理。 + String[] destStrArray = new String[]{"<",">","&","'","""}; + char despChar = '&'; // 或许可扩展兼容其他字符 + byte destBye = (byte) despChar; + List result = new ArrayList<>(); + byte[] rawContent = request.getRawContent(); + if (rawContent == null) { + FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); + log.error("rawContent is null,from:{} length:{}",fromHeader.toString(),request.getContentLength()); + return null; + } + for (int i = 0; i < rawContent.length; i++) { + if (rawContent[i] == destBye) { + boolean resul = false; + for (String destStr : destStrArray) { + if (i + destStr.length() <= rawContent.length) { + byte[] bytes = Arrays.copyOfRange(rawContent, i, i + destStr.length()); + resul = resul || (Arrays.equals(bytes,destStr.getBytes())); + } + } + if (resul) { + result.add(rawContent[i]); + } + } else { + result.add(rawContent[i]); + } + } + Byte[] bytes = new Byte[0]; + byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes)); + String content = new String(bytesResult); + //过滤掉非法字符 + String ret = XMLChars(content); + Document xml = reader.read(new ByteArrayInputStream(bytesResult)); + return xml.getRootElement(); + + } + public String XMLChars(String s) { + if (s == null || "".equals(s)) { + return s; + } + return s.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", ""); + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/UnknowReqHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/UnknowReqHandler.java new file mode 100644 index 00000000..bc98bf36 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/UnknowReqHandler.java @@ -0,0 +1,17 @@ +package com.fastbee.sip.handler.req; + +import com.fastbee.sip.handler.IReqHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Slf4j +@Component +public class UnknowReqHandler extends ReqAbstractHandler implements IReqHandler { + + @Override + public void processMsg(RequestEvent evt) { + log.warn("Unknow the method! Method:" + evt.getRequest().getMethod()); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/IMessageHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/IMessageHandler.java new file mode 100644 index 00000000..cd30a631 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/IMessageHandler.java @@ -0,0 +1,15 @@ +package com.fastbee.sip.handler.req.message; + +import com.fastbee.sip.domain.SipDevice; +import org.dom4j.Element; + +import javax.sip.RequestEvent; + +public interface IMessageHandler { + /** + * 处理来自设备的信息 + * @param evt + * @param device + */ + void handlerCmdType(RequestEvent evt, SipDevice device, Element element); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageHandlerAbstract.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageHandlerAbstract.java new file mode 100644 index 00000000..27c87af0 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageHandlerAbstract.java @@ -0,0 +1,27 @@ +package com.fastbee.sip.handler.req.message; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.util.XmlUtil; +import org.dom4j.Element; + +import javax.sip.RequestEvent; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public abstract class MessageHandlerAbstract implements IMessageHandler { + public Map messageHandlerMap = new ConcurrentHashMap<>(); + + public void addHandler(String cmdType, IMessageHandler messageHandler) { + messageHandlerMap.put(cmdType, messageHandler); + } + + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + String cmd = XmlUtil.getText(element, "CmdType"); + IMessageHandler messageHandler = messageHandlerMap.get(cmd); + if (messageHandler != null) { + messageHandler.handlerCmdType(evt, device, element); + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageRequestProcessor.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageRequestProcessor.java new file mode 100644 index 00000000..4f890fad --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/MessageRequestProcessor.java @@ -0,0 +1,92 @@ +package com.fastbee.sip.handler.req.message; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.handler.req.ReqAbstractHandler; +import com.fastbee.sip.server.IGBListener; +import com.fastbee.sip.service.ISipDeviceService; +import com.fastbee.sip.util.SipUtil; +import gov.nist.javax.sip.message.SIPRequest; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.ServerTransaction; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class MessageRequestProcessor extends ReqAbstractHandler implements InitializingBean, IReqHandler { + @Autowired + private IGBListener sipListener; + + @Autowired + private ISipDeviceService sipDeviceService; + + private static Map messageHandlerMap = new ConcurrentHashMap<>(); + + public void addHandler(String name, IMessageHandler handler) { + messageHandlerMap.put(name, handler); + } + @Override + public void afterPropertiesSet() throws Exception { + String method = "MESSAGE"; + sipListener.addRequestProcessor(method, this); + } + + @Override + public void processMsg(RequestEvent evt) { + SIPRequest sipRequest = (SIPRequest)evt.getRequest(); + String deviceId = SipUtil.getUserIdFromFromHeader(sipRequest); + ServerTransaction serverTransaction = getServerTransaction(evt); + // 查询设备是否存在 + SipDevice device = sipDeviceService.selectSipDeviceBySipId(deviceId); + try { + if (device == null ) { + // 不存在则回复404 + responseAck(serverTransaction, Response.NOT_FOUND, "device "+ deviceId +" not found"); + log.warn("[设备未找到 ]: {}", deviceId); + } else { + Element rootElement = null; + try { + rootElement = getRootElement(evt); + if (rootElement == null) { + log.error("处理MESSAGE请求 未获取到消息体{}", evt.getRequest()); + responseAck(serverTransaction, Response.BAD_REQUEST, "content is null"); + return; + } + } catch (DocumentException e) { + log.warn("解析XML消息内容异常", e); + // 不存在则回复404 + responseAck(serverTransaction, Response.BAD_REQUEST, e.getMessage()); + } + assert rootElement != null; + String name = rootElement.getName(); + //log.debug("接收到消息:" + evt.getRequest()); + IMessageHandler messageHandler = messageHandlerMap.get(name); + if ( messageHandler != null) { + messageHandler.handlerCmdType(evt, device, rootElement); + } else { + // 不支持的message + // 不存在则回复415 + responseAck(serverTransaction, Response.UNSUPPORTED_MEDIA_TYPE, "Unsupported message type, must Control/Notify/Query/Response"); + } + } + } catch (SipException e) { + log.warn("SIP 回复错误", e); + } catch (InvalidArgumentException e) { + log.warn("参数无效", e); + } catch (ParseException e) { + log.warn("SIP回复时解析异常", e); + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/NotifyMessageHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/NotifyMessageHandler.java new file mode 100644 index 00000000..29f99d0f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/NotifyMessageHandler.java @@ -0,0 +1,20 @@ +package com.fastbee.sip.handler.req.message.notify; + +import com.fastbee.sip.handler.req.message.MessageHandlerAbstract; +import com.fastbee.sip.handler.req.message.MessageRequestProcessor; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class NotifyMessageHandler extends MessageHandlerAbstract implements InitializingBean { + + @Autowired + private MessageRequestProcessor messageRequestProcessor; + + @Override + public void afterPropertiesSet() throws Exception { + String messageType = "Notify"; + messageRequestProcessor.addHandler(messageType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/AlarmHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/AlarmHandler.java new file mode 100644 index 00000000..dadaec3d --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/AlarmHandler.java @@ -0,0 +1,45 @@ +package com.fastbee.sip.handler.req.message.notify.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.notify.NotifyMessageHandler; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.server.SipMessage; +import com.fastbee.sip.server.msg.Alarm; +import com.fastbee.sip.service.IMqttService; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; +@Slf4j +@Component +public class AlarmHandler implements InitializingBean, IMessageHandler { + + @Autowired + private NotifyMessageHandler notifyMessageHandler; + + @Autowired + private MessageInvoker messageInvoker; + + @Autowired + private IMqttService mqttService; + + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + log.info("设备报警:{}", device.getDeviceId()); + SipMessage message = messageInvoker.messageToObj(evt); + if (message instanceof Alarm) { + log.info("报警内容:{}", message); + mqttService.publishEvent((Alarm) message); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "Alarm"; + notifyMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/KeepaliveHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/KeepaliveHandler.java new file mode 100644 index 00000000..695a63e7 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/KeepaliveHandler.java @@ -0,0 +1,81 @@ +package com.fastbee.sip.handler.req.message.notify.cmdType; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.ReqAbstractHandler; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.notify.NotifyMessageHandler; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.service.IMqttService; +import com.fastbee.sip.service.ISipDeviceService; +import com.fastbee.sip.util.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.ViaHeader; +import java.text.ParseException; + +@Slf4j +@Component +public class KeepaliveHandler extends ReqAbstractHandler implements InitializingBean, IMessageHandler { + + @Autowired + private NotifyMessageHandler notifyMessageHandler; + + @Autowired + private ISipDeviceService sipDeviceService; + + @Autowired + private MessageInvoker messageInvoker; + + @Autowired + private IMqttService mqttService; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + try { + Element rootElement = getRootElement(evt); + String deviceId = XmlUtil.getText(rootElement, "DeviceID"); + if (sipDeviceService.exists(deviceId)) { + ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME); + String received = viaHeader.getReceived(); + int rPort = viaHeader.getRPort(); + if (StringUtils.isEmpty(received) || rPort == -1) { + log.warn("本地地址替代! received:{},rPort:{} [{}:{}]", received, rPort, viaHeader.getHost(), viaHeader.getPort()); + received = viaHeader.getHost(); + rPort = viaHeader.getPort(); + } + device.setLastconnecttime(DateUtils.getNowDate()); + device.setIp(received); + device.setPort(rPort); + device.setHostaddress(received.concat(":").concat(String.valueOf(rPort))); + log.info("设备:{} 心跳上报时间:{}",deviceId,device.getLastconnecttime()); + //log.warn("设备:{} 心跳上报时间:{}",deviceId,device.getLastconnecttime()); + // 更新在线状态 + // sipDeviceService.updateSipDeviceStatus(device); + // 更新在线状态到emqx + mqttService.publishStatus(device, 3); + // 更新通道状态 + messageInvoker.catalogQuery(device); + // 回复200 OK + responseAck(evt); + } + + } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { + e.printStackTrace(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "Keepalive"; + notifyMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MediaStatusHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MediaStatusHandler.java new file mode 100644 index 00000000..92bed957 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MediaStatusHandler.java @@ -0,0 +1,27 @@ +package com.fastbee.sip.handler.req.message.notify.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.notify.NotifyMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.sip.RequestEvent; + +public class MediaStatusHandler implements InitializingBean, IMessageHandler { + + @Autowired + private NotifyMessageHandler notifyMessageHandler; + + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "MediaStatus"; + notifyMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MobilePositionHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MobilePositionHandler.java new file mode 100644 index 00000000..28b63739 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/notify/cmdType/MobilePositionHandler.java @@ -0,0 +1,27 @@ +package com.fastbee.sip.handler.req.message.notify.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.notify.NotifyMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.sip.RequestEvent; + +public class MobilePositionHandler implements InitializingBean, IMessageHandler { + + @Autowired + private NotifyMessageHandler notifyMessageHandler; + + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "MobilePosition"; + notifyMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/ResponseMessageHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/ResponseMessageHandler.java new file mode 100644 index 00000000..9e59516a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/ResponseMessageHandler.java @@ -0,0 +1,20 @@ +package com.fastbee.sip.handler.req.message.response; + +import com.fastbee.sip.handler.req.message.MessageHandlerAbstract; +import com.fastbee.sip.handler.req.message.MessageRequestProcessor; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean { + + @Autowired + private MessageRequestProcessor messageRequestProcessor; + + @Override + public void afterPropertiesSet() throws Exception { + String messageType = "Response"; + messageRequestProcessor.addHandler(messageType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/AlarmRHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/AlarmRHandler.java new file mode 100644 index 00000000..d6253684 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/AlarmRHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class AlarmRHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "Alarm"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/CatalogHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/CatalogHandler.java new file mode 100644 index 00000000..fb154279 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/CatalogHandler.java @@ -0,0 +1,212 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.enums.ChannelType; +import com.fastbee.sip.enums.DeviceChannelStatus; +import com.fastbee.sip.handler.req.ReqAbstractHandler; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import com.fastbee.sip.service.ISipDeviceChannelService; +import com.fastbee.sip.util.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Iterator; +import java.util.Objects; +@Slf4j +@Component +public class CatalogHandler extends ReqAbstractHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + + @Autowired + private ISipDeviceChannelService sipDeviceChannelService; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + try { + Element rootElement = getRootElement(evt); + Element deviceListElement = rootElement.element("DeviceList"); + if (deviceListElement == null) { + return; + } + Iterator deviceListIterator = deviceListElement.elementIterator(); + //List channels = new ArrayList<>(); + if (deviceListIterator != null) { + // 遍历DeviceList + while (deviceListIterator.hasNext()) { + SipDeviceChannel deviceChannel = new SipDeviceChannel(); + Element itemDevice = deviceListIterator.next(); + Element channelDeviceElement = itemDevice.element("DeviceID"); + if (channelDeviceElement == null) { + log.warn("缺少 DeviceID"); + continue; + } + String channelId = channelDeviceElement.getTextTrim(); + if (ObjectUtils.isEmpty(channelId)) { + log.warn("缺少 DeviceID"); + continue; + } + //Status + Element statusElement = itemDevice.element("Status"); + if (statusElement != null) { + String status = statusElement.getTextTrim().trim(); + if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) { + deviceChannel.setStatus(DeviceChannelStatus.online.getValue()); + } + if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) { + deviceChannel.setStatus(DeviceChannelStatus.offline.getValue()); + } + } else { + deviceChannel.setStatus(DeviceChannelStatus.offline.getValue()); + } + + ChannelType channelType = ChannelType.Other; + if (channelId.length() <= 8) { + channelType = ChannelType.CivilCode; + deviceChannel.setHasaudio(0); + }else { + if (channelId.length() == 20) { + int code = Integer.parseInt(channelId.substring(10, 13)); + switch (code){ + case 215: + channelType = ChannelType.BusinessGroup; + deviceChannel.setHasaudio(0); + break; + case 216: + channelType = ChannelType.VirtualOrganization; + deviceChannel.setHasaudio(0); + break; + case 136: + case 137: + case 138: + deviceChannel.setHasaudio(1); + break; + default: + deviceChannel.setHasaudio(0); + break; + + } + } + } + deviceChannel.setDeviceSipId(device.getDeviceSipId()); + deviceChannel.setChannelSipId(channelId); + + //name + Element channdelNameElement = itemDevice.element("Name"); + String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim() : ""; + deviceChannel.setChannelName(channelName); + //CivilCode + String civilCode = XmlUtil.getText(itemDevice, "CivilCode"); + deviceChannel.setCivilcode(civilCode); + if (channelType == ChannelType.CivilCode && civilCode == null) { + deviceChannel.setParental(1); + // 行政区划如果没有传递具体值,则推测一个 + if (channelId.length() > 2) { + deviceChannel.setCivilcode(channelId.substring(0, channelId.length() - 2)); + } + } + if (channelType.equals(ChannelType.CivilCode)) { + // 行政区划其他字段没必要识别了,默认在线即可 + deviceChannel.setStatus(1); + deviceChannel.setParental(1); + sipDeviceChannelService.updateChannel(device.getDeviceSipId(), deviceChannel); + continue; + } + //parentID + String parentId = XmlUtil.getText(itemDevice, "ParentID"); + //String businessGroupID = XmlUtil.getText(itemDevice, "BusinessGroupID"); + if (parentId != null) { + if (parentId.contains("/")) { + String lastParentId = parentId.substring(parentId.lastIndexOf("/") + 1); + deviceChannel.setParentid(lastParentId); + }else { + deviceChannel.setParentid(parentId); + } + // 兼容设备通道信息中自己为自己父节点的情况 + if (deviceChannel.getParentid().equals(deviceChannel.getChannelSipId())) { + deviceChannel.setParentid(null); + } + } + + // Parental + String parental = XmlUtil.getText(itemDevice, "Parental"); + // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1 + if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) { + deviceChannel.setParental(0); + }else { + deviceChannel.setParental(1); + } + + if (deviceChannel.getRegisterTime() == null) { + deviceChannel.setRegisterTime(DateUtils.getNowDate()); + } + deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer")); + deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model")); + deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner")); + deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block")); + deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address")); + deviceChannel.setIpaddress(XmlUtil.getText(itemDevice, "IPAddress")); + if (XmlUtil.getText(itemDevice, "Port") == null || Objects.equals(XmlUtil.getText(itemDevice, "Port"), "")) { + deviceChannel.setPort(0); + } else { + deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port"))); + } + deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password")); + + if (XmlUtil.getText(itemDevice, "Longitude") == null || Objects.equals(XmlUtil.getText(itemDevice, "Longitude"), "")) { + deviceChannel.setLongitude(BigDecimal.valueOf(0.00)); + } else { + deviceChannel.setLongitude(BigDecimal.valueOf(Double.parseDouble(XmlUtil.getText(itemDevice, "Longitude")))); + } + if (XmlUtil.getText(itemDevice, "Latitude") == null || Objects.equals(XmlUtil.getText(itemDevice, "Latitude"), "")) { + deviceChannel.setLatitude(BigDecimal.valueOf(0.00)); + } else { + deviceChannel.setLatitude(BigDecimal.valueOf(Double.parseDouble(XmlUtil.getText(itemDevice, "Latitude")))); + } + + if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) { + //兼容INFO中的信息 + Element info = itemDevice.element("Info"); + if(XmlUtil.getText(info, "PTZType") == null || "".equals(XmlUtil.getText(info, "PTZType"))){ + deviceChannel.setPtztype(0L); + } else { + deviceChannel.setPtztype(Long.parseLong(XmlUtil.getText(info, "PTZType"))); + } + } else { + deviceChannel.setPtztype(Long.parseLong(XmlUtil.getText(itemDevice, "PTZType"))); + } + + //更新到数据库 + sipDeviceChannelService.updateChannel(device.getDeviceSipId(), deviceChannel); + //channels.add(deviceChannel); + } + // 发布设备property到emqx + //mqttService.publishChannelsProperty(device.getDeviceSipId(),channels); + // 回复200 OK + responseAck(evt); + } + + } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { + e.printStackTrace(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "Catalog"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/ConfigDownloadHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/ConfigDownloadHandler.java new file mode 100644 index 00000000..fe702f47 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/ConfigDownloadHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class ConfigDownloadHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "ConfigDownload"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceConfigHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceConfigHandler.java new file mode 100644 index 00000000..5616d38f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceConfigHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class DeviceConfigHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "DeviceConfig"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceControlHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceControlHandler.java new file mode 100644 index 00000000..a7571c05 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceControlHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class DeviceControlHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "DeviceConfig"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceInfoHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceInfoHandler.java new file mode 100644 index 00000000..78aaa40b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceInfoHandler.java @@ -0,0 +1,61 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.ReqAbstractHandler; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import com.fastbee.sip.service.IMqttService; +import com.fastbee.sip.service.ISipDeviceService; +import com.fastbee.sip.util.XmlUtil; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.text.ParseException; + +@Component +public class DeviceInfoHandler extends ReqAbstractHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + + @Autowired + private ISipDeviceService sipDeviceService; + + @Autowired + private IMqttService mqttService; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + try { + Element rootElement = getRootElement(evt); + device.setDeviceName(XmlUtil.getText(rootElement, "DeviceName")); + device.setManufacturer(XmlUtil.getText(rootElement, "Manufacturer")); + device.setModel(XmlUtil.getText(rootElement, "Model")); + device.setFirmware(XmlUtil.getText(rootElement, "Firmware")); + if (StringUtils.isEmpty(device.getStreammode())) { + device.setStreammode("UDP"); + } + // 更新到数据库 + sipDeviceService.updateDevice(device); + // 发布设备info到emqx + mqttService.publishInfo(device); + // 回复200 OK + responseAck(evt); + + } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "DeviceInfo"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceStatusHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceStatusHandler.java new file mode 100644 index 00000000..9cd3d273 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/DeviceStatusHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class DeviceStatusHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "DeviceStatus"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/MobilePositionRHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/MobilePositionRHandler.java new file mode 100644 index 00000000..01faa8d8 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/MobilePositionRHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; + +@Component +public class MobilePositionRHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "MobilePosition"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/RecordInfoHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/RecordInfoHandler.java new file mode 100644 index 00000000..55778c4d --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/req/message/response/cmdType/RecordInfoHandler.java @@ -0,0 +1,93 @@ +package com.fastbee.sip.handler.req.message.response.cmdType; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.handler.req.ReqAbstractHandler; +import com.fastbee.sip.handler.req.message.IMessageHandler; +import com.fastbee.sip.handler.req.message.response.ResponseMessageHandler; +import com.fastbee.sip.model.RecordItem; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.server.RecordCacheManager; +import com.fastbee.sip.service.ISipCacheService; +import com.fastbee.sip.util.SipUtil; +import com.fastbee.sip.util.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import java.text.ParseException; +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; +@Slf4j +@Component +public class RecordInfoHandler extends ReqAbstractHandler implements InitializingBean, IMessageHandler { + + @Autowired + private ResponseMessageHandler responseMessageHandler; + + @Autowired + private ISipCacheService sipCacheService; + + @Autowired + private RecordCacheManager recordCacheManager; + @Override + public void handlerCmdType(RequestEvent evt, SipDevice device, Element element) { + try { + // 回复200 OK + responseAck(evt); + Element rootElement = getRootElement(evt); + String deviceId = rootElement.element("DeviceID").getText(); + String sn = XmlUtil.getText(rootElement, "SN"); + String sumNum = XmlUtil.getText(rootElement, "SumNum"); + String recordkey = deviceId + ":" + sn; + int recordnum = 0; + RecordList recordList = recordCacheManager.get(recordkey); + recordList.setDeviceID(deviceId); + Element recordListElement = rootElement.element("RecordList"); + if (recordListElement == null || sumNum == null || sumNum.equals("")) { + log.info("无录像数据"); + } else { + Iterator recordListIterator = recordListElement.elementIterator(); + if (recordListIterator != null) { + while (recordListIterator.hasNext()) { + Element itemRecord = recordListIterator.next(); + Element recordElement = itemRecord.element("DeviceID"); + if (recordElement == null) { + continue; + } + RecordItem item = new RecordItem(); + item.setStart(SipUtil.ISO8601Totimestamp(XmlUtil.getText(itemRecord, "StartTime"))); + item.setEnd(SipUtil.ISO8601Totimestamp(XmlUtil.getText(itemRecord, "EndTime"))); + recordList.addItem(item); + recordnum++; + } + } + } + log.info("getSumNum:{}", recordList.getSumNum()); + if (recordList.getSumNum() + recordnum == Integer.parseInt(sumNum)) { + //时间合并 保存到redia + recordList.mergeItems(); + log.info("mergeItems recordList:{}", recordList); + recordCacheManager.remove(recordkey); + sipCacheService.setRecordList(recordkey, recordList); + //发布设备property到emqx + } else { + recordList.setSumNum(recordList.getSumNum() + recordnum); + recordCacheManager.put(recordkey, recordList); + } + } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String cmdType = "RecordInfo"; + responseMessageHandler.addHandler(cmdType, this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/ByeResHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/ByeResHandler.java new file mode 100644 index 00000000..e030ee63 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/ByeResHandler.java @@ -0,0 +1,29 @@ +package com.fastbee.sip.handler.res; + +import com.fastbee.sip.handler.IResHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.ResponseEvent; +import java.text.ParseException; + +@Slf4j +@Component +public class ByeResHandler implements InitializingBean,IResHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(ResponseEvent evt) throws ParseException { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "BYE"; + sipListener.addResponseProcessor(method,this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/CancelResHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/CancelResHandler.java new file mode 100644 index 00000000..a71a5bed --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/CancelResHandler.java @@ -0,0 +1,28 @@ +package com.fastbee.sip.handler.res; + +import com.fastbee.sip.handler.IResHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.ResponseEvent; +import java.text.ParseException; +@Slf4j +@Component +public class CancelResHandler implements InitializingBean,IResHandler { + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(ResponseEvent evt) throws ParseException { + + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "CANCEL"; + sipListener.addResponseProcessor(method,this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/InviteResHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/InviteResHandler.java new file mode 100644 index 00000000..abab2f3c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/InviteResHandler.java @@ -0,0 +1,52 @@ +package com.fastbee.sip.handler.res; + +import com.fastbee.sip.handler.IResHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.Dialog; +import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; +import javax.sip.SipException; +import javax.sip.address.SipURI; +import javax.sip.header.CSeqHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.text.ParseException; + +@Slf4j +@Component +public class InviteResHandler implements InitializingBean,IResHandler { + + @Autowired + private IGBListener sipListener; + + @Override + public void processMsg(ResponseEvent evt) throws ParseException { + Response response = evt.getResponse(); + Dialog dialog = evt.getDialog(); + CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME); + Request reqAck = null; + try { + reqAck = dialog.createAck(cseq.getSeqNumber()); + SipURI requestURI = (SipURI) reqAck.getRequestURI(); + ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME); + requestURI.setHost(viaHeader.getHost()); + requestURI.setPort(viaHeader.getPort()); + reqAck.setRequestURI(requestURI); + dialog.sendAck(reqAck); + } catch (InvalidArgumentException | SipException | ParseException e) { + e.printStackTrace(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + String method = "INVITE"; + sipListener.addResponseProcessor(method,this); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/UnknowResHandler.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/UnknowResHandler.java new file mode 100644 index 00000000..2103936e --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/handler/res/UnknowResHandler.java @@ -0,0 +1,17 @@ +package com.fastbee.sip.handler.res; + +import com.fastbee.sip.handler.IResHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.sip.ResponseEvent; +import java.text.ParseException; + +@Slf4j +@Component +public class UnknowResHandler implements IResHandler { + @Override + public void processMsg(ResponseEvent evt) throws ParseException { + log.warn("Unknow Response! ReasonPhrase:" + evt.getResponse().getReasonPhrase()); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/MediaServerMapper.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/MediaServerMapper.java new file mode 100644 index 00000000..4bb2202c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/MediaServerMapper.java @@ -0,0 +1,64 @@ +package com.fastbee.sip.mapper; + +import com.fastbee.sip.domain.MediaServer; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 流媒体服务器配置Mapper接口 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Repository +public interface MediaServerMapper +{ + public MediaServer selectMediaServerById(Long id); + /** + * 查询流媒体服务器配置 + * + * @return 流媒体服务器配置 + */ + public List selectMediaServer(MediaServer mediaServer); + public List selectMediaServerBytenantId(Long tenantId); + /** + * 查询流媒体服务器配置列表 + * + * @param mediaServer 流媒体服务器配置 + * @return 流媒体服务器配置集合 + */ + public List selectMediaServerList(MediaServer mediaServer); + + /** + * 新增流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + public int insertMediaServer(MediaServer mediaServer); + + /** + * 修改流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + public int updateMediaServer(MediaServer mediaServer); + + /** + * 删除流媒体服务器配置 + * + * @param id 流媒体服务器配置主键 + * @return 结果 + */ + public int deleteMediaServerById(Long id); + + /** + * 批量删除流媒体服务器配置 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMediaServerByIds(Long[] ids); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipConfigMapper.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipConfigMapper.java new file mode 100644 index 00000000..1b5e4b3d --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipConfigMapper.java @@ -0,0 +1,66 @@ +package com.fastbee.sip.mapper; + +import com.fastbee.sip.domain.SipConfig; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * sip系统配置Mapper接口 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Repository +public interface SipConfigMapper +{ + /** + * 查询产品下第一条sip系统配置 + * + * @return sip系统配置 + */ + public SipConfig selectSipConfigByProductId(Long productId); + /** + * 查询sip系统配置列表 + * + * @param sipConfig sip系统配置 + * @return sip系统配置集合 + */ + public List selectSipConfigList(SipConfig sipConfig); + + /** + * 新增sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + public int insertSipConfig(SipConfig sipConfig); + + /** + * 修改sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + public int updateSipConfig(SipConfig sipConfig); + + public int resetDefaultSipConfig(); + + /** + * 删除sip系统配置 + * + * @param id sip系统配置主键 + * @return 结果 + */ + public int deleteSipConfigById(Long id); + + /** + * 批量删除sip系统配置 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSipConfigByIds(Long[] ids); + + public int deleteSipConfigByProductId(Long[] productIds); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceChannelMapper.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceChannelMapper.java new file mode 100644 index 00000000..28d16bde --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceChannelMapper.java @@ -0,0 +1,76 @@ +package com.fastbee.sip.mapper; + +import com.fastbee.sip.domain.SipDeviceChannel; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 监控设备通道信息Mapper接口 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Repository +public interface SipDeviceChannelMapper { + /** + * 查询监控设备通道信息 + * + * @param channelId 监控设备通道信息主键 + * @return 监控设备通道信息 + */ + public SipDeviceChannel selectSipDeviceChannelById(Long channelId); + + public SipDeviceChannel selectSipDeviceChannelByChannelSipId(String channelSipId); + + public List selectSipDeviceChannelByDeviceSipId(String deviceSipId); + + /** + * 查询监控设备通道信息列表 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 监控设备通道信息集合 + */ + public List selectSipDeviceChannelList(SipDeviceChannel sipDeviceChannel); + List selectChannelWithCivilCodeAndLength(@Param("deviceSipId") String deviceSipId, @Param("parentId") String parentId, @Param("length")Integer length); + + public List selectChannelByCivilCode(@Param("deviceSipId") String deviceSipId, @Param("parentId") String parentId); + List selectChannelWithoutCiviCode(String deviceId); + Integer getChannelMinLength(String deviceSipId); + /** + * 新增监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + public int insertSipDeviceChannel(SipDeviceChannel sipDeviceChannel); + + /** + * 修改监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + public int updateSipDeviceChannel(SipDeviceChannel sipDeviceChannel); + + public int updateChannelStreamId(SipDeviceChannel sipDeviceChannel); + + /** + * 删除监控设备通道信息 + * + * @param channelId 监控设备通道信息主键 + * @return 结果 + */ + public int deleteSipDeviceChannelById(Long channelId); + + /** + * 批量删除监控设备通道信息 + * + * @param channelIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSipDeviceChannelByIds(Long[] channelIds); + + public int deleteSipDeviceChannelByDeviceId(String deviceSipId); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceMapper.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceMapper.java new file mode 100644 index 00000000..d84875f3 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/mapper/SipDeviceMapper.java @@ -0,0 +1,77 @@ +package com.fastbee.sip.mapper; + +import com.fastbee.sip.domain.SipDevice; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 监控设备Mapper接口 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Repository +public interface SipDeviceMapper +{ + /** + * 查询监控设备 + * + * @param deviceId 监控设备主键 + * @return 监控设备 + */ + public SipDevice selectSipDeviceByDeviceId(Long deviceId); + + + public SipDevice selectSipDeviceBySipId(String sipId); + /** + * 查询监控设备列表 + * + * @param sipDevice 监控设备 + * @return 监控设备集合 + */ + public List selectSipDeviceList(SipDevice sipDevice); + public List selectOfflineSipDevice(Integer timeout); + + /** + * 新增监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + int insertSipDevice(SipDevice sipDevice); + + /** + * 修改监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + int updateSipDevice(SipDevice sipDevice); + + /** + * 更新设备状态 + * + * @param sipDevice 设备 + * @return 结果 + */ + public int updateSipDeviceStatus(SipDevice sipDevice); + + /** + * 删除监控设备 + * + * @param deviceId 监控设备主键 + * @return 结果 + */ + public int deleteSipDeviceByDeviceId(Long deviceId); + + /** + * 批量删除监控设备 + * + * @param deviceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSipDeviceByDeviceIds(Long[] deviceIds); + + public int deleteSipDeviceByByDeviceSipId(String deviceSipId); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/BaseTree.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/BaseTree.java new file mode 100644 index 00000000..4a711950 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/BaseTree.java @@ -0,0 +1,35 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.text.Collator; +import java.util.Comparator; +@Data +public class BaseTree implements Comparable{ + private String id; + private String deviceId; + private String pid; + private String name; + private boolean parent; + private T basicData; + @Override + public int compareTo(@NotNull BaseTree treeNode) { + if (this.parent || treeNode.isParent()) { + if (!this.parent && !treeNode.isParent()) { + Comparator cmp = Collator.getInstance(java.util.Locale.CHINA); + return cmp.compare(treeNode.getName(), this.getName()); + }else { + if (this.isParent()) { + return 1; + }else { + return -1; + } + } + }else{ + Comparator cmp = Collator.getInstance(java.util.Locale.CHINA); + return cmp.compare(treeNode.getName(), this.getName()); + } + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/GB28181DeviceChannel.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/GB28181DeviceChannel.java new file mode 100644 index 00000000..b0e98bd9 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/GB28181DeviceChannel.java @@ -0,0 +1,209 @@ +package com.fastbee.sip.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.enums.ChannelStatus; +import com.fastbee.sip.enums.PTZType; +import com.fastbee.sip.util.SipUtil; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.StringJoiner; + +@Data +public class GB28181DeviceChannel { + /** + * 通道对应的设备ID + */ + @JsonIgnore + private String deviceId; + + /** + * 通道类型 + */ + @JacksonXmlProperty(isAttribute = true, localName = "itemType") + private String type; + + /** + * 通道id + */ + @JacksonXmlProperty(localName = "DeviceID") + private String channelId; + + /** + * 通道名 + */ + @JacksonXmlProperty(localName = "Name") + private String name; + + /** + * 生产厂商 + */ + @JacksonXmlProperty(localName = "Manufacturer") + private String manufacturer; + + /** + * 型号 + */ + @JacksonXmlProperty(localName = "Model") + private String model; + + /** + * 设备归属 + */ + @JacksonXmlProperty(localName = "Owner") + private String owner; + + /** + * 行政区域 + */ + @JacksonXmlProperty(localName = "CivilCode") + private String civilCode; + + /** + * 警区 + */ + @JacksonXmlProperty(localName = "Block") + private String block; + + /** + * 安装地址 + */ + @JacksonXmlProperty(localName = "Address") + private String address; + + /** + * 是否有子设备 1有, 0没有 + */ + @JacksonXmlProperty(localName = "Parental") + private int parental; + + /** + * 父级id + */ + @JacksonXmlProperty(localName = "ParentID") + private String parentId; + + /** + * 信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式 + */ + @JacksonXmlProperty(localName = "SafetyWay") + private int safetyWay; + + /** + * 注册方式 缺省为1;1:符合IETF RFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式 + */ + @JacksonXmlProperty(localName = "RegisterWay") + private String registerWay; + + /** + * 证书序列号 + */ + @JacksonXmlProperty(localName = "CertNum") + private String certNum; + + /** + * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效 + */ + @JacksonXmlProperty(localName = "Certifiable") + private int certifiable; + + /** + * 证书无效原因码 + */ + @JacksonXmlProperty(localName = "ErrCode") + private int errCode; + + /** + * 证书终止有效期 + */ + @JacksonXmlProperty(localName = "EndTime") + private String endTime; + + /** + * 保密属性 缺省为0; 0:不涉密, 1:涉密 + */ + @JacksonXmlProperty(localName = "Secrecy") + private String secrecy; + + /** + * IP地址 + */ + @JacksonXmlProperty(localName = "IpAddress") + private String ipAddress; + + /** + * 端口号 + */ + @JacksonXmlProperty(localName = "Port") + private int port; + + /** + * 密码 + */ + @JacksonXmlProperty(localName = "Password") + private String password; + + /** + * 详情信息 + */ + @JacksonXmlProperty(localName = "Info") + private Info info; + + /** + * 经度 + */ + @JacksonXmlProperty(localName = "Longitude") + private double longitude; + + /** + * 纬度 + */ + @JacksonXmlProperty(localName = "Latitude") + private double latitude; + + /** + * 子设备数 + */ + @JacksonXmlProperty(localName = "SubCount") + private int subCount; + + @JacksonXmlProperty(localName = "Status") + private ChannelStatus status; + + //目录类型: 0目录,1设备通道? + @JacksonXmlProperty(localName = "CatalogType") + private String catalogType; + + @JacksonXmlProperty(localName = "Event") + private String event; + + @JacksonXmlProperty(localName = "BusinessGroupID") + private String businessGroupId; + + @Getter + @Setter + public static class Info { + + /** + * 云台类型 + */ + @JacksonXmlProperty(localName = "PTZType") + private PTZType PTZType; + + @JacksonXmlProperty(localName = "DownloadSpeed") + private String downloadSpeed; + + public String toXML() { + StringJoiner joiner = new StringJoiner(""); + joiner + .add("") + .add(PTZType == null ? "0" : String.valueOf(getPTZType().getValue())) + .add("\n"); + joiner.add("").add(SipUtil.safeString(downloadSpeed)).add("\n"); + + return joiner.toString(); + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/InviteInfo.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/InviteInfo.java new file mode 100644 index 00000000..b1ca6a9b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/InviteInfo.java @@ -0,0 +1,14 @@ +package com.fastbee.sip.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class InviteInfo { + private String ssrc; + private String callId; + private String fromTag; + private String viaTag; + private int port; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/MediaServerConfig.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/MediaServerConfig.java new file mode 100644 index 00000000..41525bff --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/MediaServerConfig.java @@ -0,0 +1,203 @@ +package com.fastbee.sip.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class MediaServerConfig { + @JSONField(name = "api.apiDebug") + private String apiDebug; + + @JSONField(name = "api.secret") + private String apiSecret; + + @JSONField(name = "ffmpeg.bin") + private String ffmpegBin; + + @JSONField(name = "ffmpeg.cmd") + private String ffmpegCmd; + + @JSONField(name = "ffmpeg.log") + private String ffmpegLog; + + @JSONField(name = "general.enableVhost") + private String generalEnableVhost; + + @JSONField(name = "general.flowThreshold") + private String generalFlowThreshold; + + @JSONField(name = "general.maxStreamWaitMS") + private String generalMaxStreamWaitMS; + + @JSONField(name = "general.streamNoneReaderDelayMS") + private String generalStreamNoneReaderDelayMS; + + private String localIP; + + private String wanIp; + + @JSONField(name = "hls.fileBufSize") + private String hlsFileBufSize; + + @JSONField(name = "hls.filePath") + private String hlsFilePath; + + @JSONField(name = "hls.segDur") + private String hlsSegDur; + + @JSONField(name = "hls.segNum") + private String hlsSegNum; + + @JSONField(name = "hook.access_file_except_hls") + private String hookAccessFileExceptHLS; + + @JSONField(name = "hook.admin_params") + private String hookAdminParams; + + @JSONField(name = "hook.enable") + private String hookEnable; + + @JSONField(name = "hook.on_flow_report") + private String hookOnFlowReport; + + @JSONField(name = "hook.on_http_access") + private String hookOnHttpAccess; + + @JSONField(name = "hook.on_play") + private String hookOnPlay; + + @JSONField(name = "hook.on_publish") + private String hookOnPublish; + + @JSONField(name = "hook.on_record_mp4") + private String hookOnRecordMp4; + + @JSONField(name = "hook.on_rtsp_auth") + private String hookOnRtspAuth; + + @JSONField(name = "hook.on_rtsp_realm") + private String hookOnRtspRealm; + + @JSONField(name = "hook.on_shell_login") + private String hookOnShellLogin; + + @JSONField(name = "hook.on_stream_changed") + private String hookOnStreamChanged; + + @JSONField(name = "hook.on_stream_none_reader") + private String hookOnStreamNoneReader; + + @JSONField(name = "hook.on_stream_not_found") + private String hookOnStreamNotFound; + + @JSONField(name = "hook.timeoutSec") + private String hookTimeoutSec; + + @JSONField(name = "http.charSet") + private String httpCharSet; + + @JSONField(name = "http.keepAliveSecond") + private String httpKeepAliveSecond; + + @JSONField(name = "http.maxReqCount") + private String httpMaxReqCount; + + @JSONField(name = "http.maxReqSize") + private String httpMaxReqSize; + + @JSONField(name = "http.notFound") + private String httpNotFound; + + @JSONField(name = "http.port") + private String httpPort; + + @JSONField(name = "http.rootPath") + private String httpRootPath; + + @JSONField(name = "http.sendBufSize") + private String httpSendBufSize; + + @JSONField(name = "http.sslport") + private String httpSSLport; + + @JSONField(name = "multicast.addrMax") + private String multicastAddrMax; + + @JSONField(name = "multicast.addrMin") + private String multicastAddrMin; + + @JSONField(name = "multicast.udpTTL") + private String multicastUdpTTL; + + @JSONField(name = "record.appName") + private String recordAppName; + + @JSONField(name = "record.filePath") + private String recordFilePath; + + @JSONField(name = "record.fileSecond") + private String recordFileSecond; + + @JSONField(name = "record.sampleMS") + private String recordFileSampleMS; + + @JSONField(name = "rtmp.handshakeSecond") + private String rtmpHandshakeSecond; + + @JSONField(name = "rtmp.keepAliveSecond") + private String rtmpKeepAliveSecond; + + @JSONField(name = "rtmp.modifyStamp") + private String rtmpModifyStamp; + + @JSONField(name = "rtmp.port") + private String rtmpPort; + + @JSONField(name = "rtp.audioMtuSize") + private String rtpAudioMtuSize; + + @JSONField(name = "rtp.clearCount") + private String rtpClearCount; + + @JSONField(name = "rtp.cycleMS") + private String rtpCycleMS; + + @JSONField(name = "rtp.maxRtpCount") + private String rtpMaxRtpCount; + + @JSONField(name = "rtp.videoMtuSize") + private String rtpVideoMtuSize; + + @JSONField(name = "rtp_proxy.checkSource") + private String rtpProxyCheckSource; + + @JSONField(name = "rtp_proxy.dumpDir") + private String rtpProxyDumpDir; + + @JSONField(name = "rtp_proxy.port") + private String rtpProxyPort; + + @JSONField(name = "rtp_proxy.timeoutSec") + private String rtpProxyTimeoutSec; + + @JSONField(name = "rtsp.authBasic") + private String rtspAuthBasic; + + @JSONField(name = "rtsp.handshakeSecond") + private String rtspHandshakeSecond; + + @JSONField(name = "rtsp.keepAliveSecond") + private String rtspKeepAliveSecond; + + @JSONField(name = "rtsp.port") + private String rtspPort; + + @JSONField(name = "rtsp.sslport") + private String rtspSSlport; + + @JSONField(name = "shell.maxReqSize") + private String shellMaxReqSize; + + @JSONField(name = "shell.shell") + private String shellPhell; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzDirectionInput.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzDirectionInput.java new file mode 100644 index 00000000..14855b56 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzDirectionInput.java @@ -0,0 +1,10 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +@Data +public class PtzDirectionInput { + int leftRight; + int upDown; + int moveSpeed; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzscaleInput.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzscaleInput.java new file mode 100644 index 00000000..09de1c00 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/PtzscaleInput.java @@ -0,0 +1,9 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +@Data +public class PtzscaleInput{ + int inOut; + int scaleSpeed; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordInput.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordInput.java new file mode 100644 index 00000000..baf68d9b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordInput.java @@ -0,0 +1,11 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +@Data +public class RecordInput { + String deviceId; + String channelId; + String startTime; + String endTime; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordItem.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordItem.java new file mode 100644 index 00000000..fe6286d3 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordItem.java @@ -0,0 +1,23 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Data +public class RecordItem implements Comparable{ + long start; + long end; + @Override + public int compareTo(RecordItem item) { + Date startTime_now = new Date(start*1000); + Date startTime_param = new Date(item.getStart()*1000); + if (startTime_param.compareTo(startTime_now) > 0) { + return -1; + }else { + return 1; + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordList.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordList.java new file mode 100644 index 00000000..870fc7d3 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RecordList.java @@ -0,0 +1,56 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +@Data +public class RecordList { + String deviceID; + int sumNum; + private List recordItems = new ArrayList<>(); + + public void addItem(RecordItem item) { + this.recordItems.add(item); + } + + public void mergeItems() { + recordItems.sort(Comparator.naturalOrder()); + List temp = new ArrayList<>(); + long start = 0; + long end = 0; + for (int i = 0; i < recordItems.size(); i++) { + RecordItem item = recordItems.get(i); + if (i == recordItems.size() - 1) { + if (end >= item.getStart()) { + RecordItem titem = new RecordItem(); + titem.setStart(start); + titem.setEnd(item.getEnd()); + temp.add(titem); + } else { + RecordItem titem = new RecordItem(); + titem.setStart(start); + titem.setEnd(end); + temp.add(titem); + temp.add(item); + } + } + if (start == 0 && end == 0) { + start = item.getStart(); + end = item.getEnd(); + } else if (end >= item.getStart()) { + end = item.getEnd(); + } else { + RecordItem titem = new RecordItem(); + titem.setStart(start); + titem.setEnd(end); + temp.add(titem); + start = item.getStart(); + end = item.getEnd(); + } + } + this.recordItems = temp; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RequestMessage.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RequestMessage.java new file mode 100644 index 00000000..ee534165 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/RequestMessage.java @@ -0,0 +1,11 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +@Data +public class RequestMessage { + private String id; + private String deviceId; + private String type; + private Object data; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDate.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDate.java new file mode 100644 index 00000000..56a13a67 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDate.java @@ -0,0 +1,141 @@ +package com.fastbee.sip.model; + +import gov.nist.core.InternalErrorHandler; +import gov.nist.javax.sip.header.SIPDate; + +import java.util.*; + +public class SipDate extends SIPDate { + + private final Calendar javaCal; + + public SipDate(long timeMillis) { + this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault()); + Date date = new Date(timeMillis); + this.javaCal.setTime(date); + this.wkday = this.javaCal.get(Calendar.DAY_OF_WEEK); + switch(this.wkday) { + case 1: + this.sipWkDay = "Sun"; + break; + case 2: + this.sipWkDay = "Mon"; + break; + case 3: + this.sipWkDay = "Tue"; + break; + case 4: + this.sipWkDay = "Wed"; + break; + case 5: + this.sipWkDay = "Thu"; + break; + case 6: + this.sipWkDay = "Fri"; + break; + case 7: + this.sipWkDay = "Sat"; + break; + default: + InternalErrorHandler.handleException("No date map for wkday " + this.wkday); + } + + this.day = this.javaCal.get(Calendar.DATE); + this.month = this.javaCal.get(Calendar.MONTH); + switch(this.month) { + case 0: + this.sipMonth = "Jan"; + break; + case 1: + this.sipMonth = "Feb"; + break; + case 2: + this.sipMonth = "Mar"; + break; + case 3: + this.sipMonth = "Apr"; + break; + case 4: + this.sipMonth = "May"; + break; + case 5: + this.sipMonth = "Jun"; + break; + case 6: + this.sipMonth = "Jul"; + break; + case 7: + this.sipMonth = "Aug"; + break; + case 8: + this.sipMonth = "Sep"; + break; + case 9: + this.sipMonth = "Oct"; + break; + case 10: + this.sipMonth = "Nov"; + break; + case 11: + this.sipMonth = "Dec"; + break; + default: + InternalErrorHandler.handleException("No date map for month " + this.month); + } + + this.year = this.javaCal.get(Calendar.YEAR); + this.hour = this.javaCal.get(Calendar.HOUR_OF_DAY); + this.minute = this.javaCal.get(Calendar.MINUTE); + this.second = this.javaCal.get(Calendar.SECOND); + } + + @Override + public StringBuilder encode(StringBuilder var1) { + String var2; + if (this.month < 9) { + var2 = "0" + (this.month + 1); + } else { + var2 = "" + (this.month + 1); + } + + String var3; + if (this.day < 10) { + var3 = "0" + this.day; + } else { + var3 = "" + this.day; + } + + String var4; + if (this.hour < 10) { + var4 = "0" + this.hour; + } else { + var4 = "" + this.hour; + } + + String var5; + if (this.minute < 10) { + var5 = "0" + this.minute; + } else { + var5 = "" + this.minute; + } + + String var6; + if (this.second < 10) { + var6 = "0" + this.second; + } else { + var6 = "" + this.second; + } + + int var8 = this.javaCal.get(Calendar.MILLISECOND); + String var7; + if (var8 < 10) { + var7 = "00" + var8; + } else if (var8 < 100) { + var7 = "0" + var8; + } else { + var7 = "" + var8; + } + + return var1.append(this.year).append("-").append(var2).append("-").append(var3).append("T").append(var4).append(":").append(var5).append(":").append(var6).append(".").append(var7); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDeviceSummary.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDeviceSummary.java new file mode 100644 index 00000000..ba9f9b22 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/SipDeviceSummary.java @@ -0,0 +1,25 @@ +package com.fastbee.sip.model; + +import com.fastbee.sip.domain.SipDevice; +import lombok.Data; + +@Data +public class SipDeviceSummary { + public SipDeviceSummary(SipDevice device) { + this.manufacturer = device.getManufacturer(); + this.firmware = device.getFirmware(); + this.transport = device.getTransport(); + this.streammode = device.getStreammode(); + this.port = device.getPort(); + this.hostaddress = device.getHostaddress(); + } + public SipDeviceSummary() { + + } + private String manufacturer; + private String firmware; + private String transport; + private String streammode; + private Integer port; + private String hostaddress; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/Stream.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/Stream.java new file mode 100644 index 00000000..0b8859c9 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/Stream.java @@ -0,0 +1,41 @@ +package com.fastbee.sip.model; + +import com.alibaba.fastjson.JSONArray; +import lombok.Data; + +@Data +public class Stream { + private String app; + private String deviceID; + private String channelId; + private String ssrc; + private String streamId; + private String ip; + private String playurl; + private String mediaServerId; + private JSONArray tracks; + private String startTime; + private String endTime; + private boolean pause; + + private String flv; + private String https_flv; + private String ws_flv; + private String fmp4; + private String https_fmp4; + private String ws_fmp4; + private String hls; + private String https_hls; + private String ws_hls; + private String rtmp; + private String rtsp; + private String rtc; + + public Stream(String deviceSipId, String channelId, String streamId, String ssrc) { + this.deviceID = deviceSipId; + this.channelId = channelId; + this.streamId = streamId; + this.ssrc = ssrc; + } + public Stream() {} +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/StreamURL.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/StreamURL.java new file mode 100644 index 00000000..f00b5d37 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/StreamURL.java @@ -0,0 +1,31 @@ +package com.fastbee.sip.model; + +import lombok.Data; + +@Data +public class StreamURL { + private String protocol; + private String host; + private int port = -1; + private String file; + private String url; + + public StreamURL() { + } + + public StreamURL(String protocol, String host, int port, String file) { + this.protocol = protocol; + this.host = host; + this.port = port; + this.file = file; + } + + @Override + public String toString() { + if (protocol != null && host != null && port != -1 ) { + return String.format("%s://%s:%s/%s", protocol, host, port, file); + }else { + return null; + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/VideoSessionInfo.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/VideoSessionInfo.java new file mode 100644 index 00000000..1b68813f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/VideoSessionInfo.java @@ -0,0 +1,34 @@ +package com.fastbee.sip.model; + +import com.fastbee.sip.enums.SessionType; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class VideoSessionInfo { + //流基本信息 + private String mediaServerId; + private String deviceId; + private String channelId; + private String stream; + private String ssrc; + private int port; + private String streamMode; + private SessionType type; + //流状态 + private boolean pushing; + private boolean recording; + private int onPlaytime; + private int player; + private boolean videoRecord; + private boolean enable_fmp4; + private boolean enable_hls; + private boolean enable_rtmp; + private boolean enable_rtsp; + + //录像信息 + String startTime; + String endTime; + int downloadSpeed; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/ZlmMediaServer.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/ZlmMediaServer.java new file mode 100644 index 00000000..7e3a97b6 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/model/ZlmMediaServer.java @@ -0,0 +1,206 @@ +package com.fastbee.sip.model; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +@Data +public class ZlmMediaServer { + @JSONField(name = "api.apiDebug") + private String apiDebug; + + @JSONField(name = "api.secret") + private String apiSecret; + + @JSONField(name = "ffmpeg.bin") + private String ffmpegBin; + + @JSONField(name = "ffmpeg.cmd") + private String ffmpegCmd; + + @JSONField(name = "ffmpeg.log") + private String ffmpegLog; + + @JSONField(name = "general.mediaServerId") + private String mediaServerId; + + @JSONField(name = "general.enableVhost") + private String generalEnableVhost; + + @JSONField(name = "general.flowThreshold") + private String generalFlowThreshold; + + @JSONField(name = "general.maxStreamWaitMS") + private String generalMaxStreamWaitMS; + + @JSONField(name = "general.streamNoneReaderDelayMS") + private String generalStreamNoneReaderDelayMS; + + private String localIP; + + private String wanIp; + + @JSONField(name = "hls.fileBufSize") + private String hlsFileBufSize; + + @JSONField(name = "hls.filePath") + private String hlsFilePath; + + @JSONField(name = "hls.segDur") + private String hlsSegDur; + + @JSONField(name = "hls.segNum") + private String hlsSegNum; + + @JSONField(name = "hook.access_file_except_hls") + private String hookAccessFileExceptHLS; + + @JSONField(name = "hook.admin_params") + private String hookAdminParams; + + @JSONField(name = "hook.enable") + private String hookEnable; + + @JSONField(name = "hook.on_flow_report") + private String hookOnFlowReport; + + @JSONField(name = "hook.on_http_access") + private String hookOnHttpAccess; + + @JSONField(name = "hook.on_play") + private String hookOnPlay; + + @JSONField(name = "hook.on_publish") + private String hookOnPublish; + + @JSONField(name = "hook.on_record_mp4") + private String hookOnRecordMp4; + + @JSONField(name = "hook.on_rtsp_auth") + private String hookOnRtspAuth; + + @JSONField(name = "hook.on_rtsp_realm") + private String hookOnRtspRealm; + + @JSONField(name = "hook.on_shell_login") + private String hookOnShellLogin; + + @JSONField(name = "hook.on_stream_changed") + private String hookOnStreamChanged; + + @JSONField(name = "hook.on_stream_none_reader") + private String hookOnStreamNoneReader; + + @JSONField(name = "hook.on_stream_not_found") + private String hookOnStreamNotFound; + + @JSONField(name = "hook.timeoutSec") + private String hookTimeoutSec; + + @JSONField(name = "http.charSet") + private String httpCharSet; + + @JSONField(name = "http.keepAliveSecond") + private String httpKeepAliveSecond; + + @JSONField(name = "http.maxReqCount") + private String httpMaxReqCount; + + @JSONField(name = "http.maxReqSize") + private String httpMaxReqSize; + + @JSONField(name = "http.notFound") + private String httpNotFound; + + @JSONField(name = "http.port") + private String httpPort; + + @JSONField(name = "http.rootPath") + private String httpRootPath; + + @JSONField(name = "http.sendBufSize") + private String httpSendBufSize; + + @JSONField(name = "http.sslport") + private String httpSSLport; + + @JSONField(name = "multicast.addrMax") + private String multicastAddrMax; + + @JSONField(name = "multicast.addrMin") + private String multicastAddrMin; + + @JSONField(name = "multicast.udpTTL") + private String multicastUdpTTL; + + @JSONField(name = "record.appName") + private String recordAppName; + + @JSONField(name = "record.filePath") + private String recordFilePath; + + @JSONField(name = "record.fileSecond") + private String recordFileSecond; + + @JSONField(name = "record.sampleMS") + private String recordFileSampleMS; + + @JSONField(name = "rtmp.handshakeSecond") + private String rtmpHandshakeSecond; + + @JSONField(name = "rtmp.keepAliveSecond") + private String rtmpKeepAliveSecond; + + @JSONField(name = "rtmp.modifyStamp") + private String rtmpModifyStamp; + + @JSONField(name = "rtmp.port") + private String rtmpPort; + + @JSONField(name = "rtp.audioMtuSize") + private String rtpAudioMtuSize; + + @JSONField(name = "rtp.clearCount") + private String rtpClearCount; + + @JSONField(name = "rtp.cycleMS") + private String rtpCycleMS; + + @JSONField(name = "rtp.maxRtpCount") + private String rtpMaxRtpCount; + + @JSONField(name = "rtp.videoMtuSize") + private String rtpVideoMtuSize; + + @JSONField(name = "rtp_proxy.checkSource") + private String rtpProxyCheckSource; + + @JSONField(name = "rtp_proxy.dumpDir") + private String rtpProxyDumpDir; + + @JSONField(name = "rtp_proxy.port") + private String rtpProxyPort; + + @JSONField(name = "rtp_proxy.timeoutSec") + private String rtpProxyTimeoutSec; + + @JSONField(name = "rtsp.authBasic") + private String rtspAuthBasic; + + @JSONField(name = "rtsp.handshakeSecond") + private String rtspHandshakeSecond; + + @JSONField(name = "rtsp.keepAliveSecond") + private String rtspKeepAliveSecond; + + @JSONField(name = "rtsp.port") + private String rtspPort; + + @JSONField(name = "rtsp.sslport") + private String rtspSSlport; + + @JSONField(name = "shell.maxReqSize") + private String shellMaxReqSize; + + @JSONField(name = "shell.shell") + private String shellPhell; +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IGBListener.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IGBListener.java new file mode 100644 index 00000000..9f7d4252 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IGBListener.java @@ -0,0 +1,11 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.handler.IResHandler; + +import javax.sip.SipListener; + +public interface IGBListener extends SipListener { + public void addRequestProcessor(String method, IReqHandler processor); + public void addResponseProcessor(String method, IResHandler processor); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IRtspCmd.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IRtspCmd.java new file mode 100644 index 00000000..435b1a7a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/IRtspCmd.java @@ -0,0 +1,11 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.domain.SipDevice; + +public interface IRtspCmd { + void playPause(SipDevice device, String channelId, String streamId); + void playReplay(SipDevice device, String channelId, String streamId); + void playBackSeek(SipDevice device, String channelId, String streamId, long seektime); + void playBackSpeed(SipDevice device, String channelId, String streamId, Integer speed); + void setCseq(String streamId); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ISipCmd.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ISipCmd.java new file mode 100644 index 00000000..85da8a75 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ISipCmd.java @@ -0,0 +1,12 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.model.VideoSessionInfo; + +public interface ISipCmd { + VideoSessionInfo playStreamCmd(SipDevice device, String channelId, boolean record); + VideoSessionInfo playbackStreamCmd(SipDevice device, String channelId, String startTime, String endTime); + VideoSessionInfo downloadStreamCmd(SipDevice device, String channelId, String startTime, String endTime, int downloadSpeed); + void streamByeCmd(SipDevice device, String channelId, String stream, String ssrc); + void streamByeCmd(String deviceId, String channelId, String stream, String ssrc); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/MessageInvoker.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/MessageInvoker.java new file mode 100644 index 00000000..1e433226 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/MessageInvoker.java @@ -0,0 +1,106 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.server.msg.ConfigDownload; +import com.fastbee.sip.server.msg.DeviceControl; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.sip.ClientTransaction; +import javax.sip.PeerUnavailableException; +import javax.sip.RequestEvent; +import javax.sip.message.Request; +import java.util.Date; + +public interface MessageInvoker { + + /** + * 发送订阅报警指令 + * + * @param device 设备信息 + * @param startAlarmPriority 开始报警级别 + * @param endAlarmPriority 结束报警级别 + * @param expires 过期时间 + * @param alarmMethod 报警方式 + * @param startAlarmTime 开始报警时间 + * @param endAlarmTime 结束报警时间 + * @return void + */ + void subscribe(@Nonnull SipDevice device, + int startAlarmPriority, + int endAlarmPriority, + int expires, + @Nonnull String alarmMethod, + @Nullable Date startAlarmTime, + @Nullable Date endAlarmTime); + + /** + * 发送设备控制指令,{@link DeviceControl#getDeviceId()}为通道ID + * + * @param device 设备信息 + * @param command 控制指令 + * @return void + */ + void deviceControl(SipDevice device, DeviceControl command); + + /** + * 获取设备详细信息 + * + * @param device 设备信息 + * @return 详细信息 + */ + boolean deviceInfoQuery(SipDevice device); + + /** + * 获取通道列表 + * + * @param device 设备信息 + * @return 通道列表 + */ + boolean catalogQuery(SipDevice device); + + boolean recordInfoQuery(SipDevice device, String sn, String channelId, Date start, Date end); + + + /** + * 订阅通道目录,发送订阅请求后 + * + * @param device device + * @param from 订阅有效期从 + * @param to 订阅有效期止 + * @return void + */ + void subscribeCatalog(SipDevice device, Date from, Date to) throws PeerUnavailableException; + + + /** + * 下载设备配置信息,可指定配置类型,如果未指定类型则获取所有类型的配置 + * + * @param device 设备信息 + * @param configType 要下载的配置类型 + * @return 配置信息 + */ + ConfigDownload downloadConfig(SipDevice device, ConfigDownload.ConfigType... configType); + SipMessage messageToObj(RequestEvent event); + /** + * 发送SIP原始请求 + * + * @param device 设备 + * @param request 原始请求 + * @param awaitAck 是否等待响应 + * @return 事务信息 + */ + ClientTransaction request(SipDevice device, Request request, boolean awaitAck); + + /** + * 发起一个请求,并等待响应,不同的请求方式以及内容,响应的内容不同。 + * + * @param transaction ClientTransaction + * @param request Request + * @param awaitAck 是否等待响应 + * @return 响应结果 + */ + Object request(ClientTransaction transaction, Request request, boolean awaitAck); + + T getExecResult(String key, long timeout); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/NullSipProvider.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/NullSipProvider.java new file mode 100644 index 00000000..9a879cd1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/NullSipProvider.java @@ -0,0 +1,89 @@ +package com.fastbee.sip.server; + +import javax.sip.*; +import javax.sip.header.CallIdHeader; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.util.TooManyListenersException; + +public class NullSipProvider implements SipProvider { + @Override + public void addSipListener(SipListener sipListener) throws TooManyListenersException { + + } + + @Override + public void removeSipListener(SipListener sipListener) { + + } + + @Override + public SipStack getSipStack() { + return null; + } + + @Override + public ListeningPoint getListeningPoint() { + return null; + } + + @Override + public ListeningPoint[] getListeningPoints() { + return new ListeningPoint[0]; + } + + @Override + public void setListeningPoint(ListeningPoint listeningPoint) throws ObjectInUseException { + + } + + @Override + public void addListeningPoint(ListeningPoint listeningPoint) throws ObjectInUseException, TransportAlreadySupportedException { + + } + + @Override + public ListeningPoint getListeningPoint(String s) { + return null; + } + + @Override + public void removeListeningPoint(ListeningPoint listeningPoint) throws ObjectInUseException { + + } + + @Override + public CallIdHeader getNewCallId() { + return null; + } + + @Override + public ClientTransaction getNewClientTransaction(Request request) throws TransactionUnavailableException { + return null; + } + + @Override + public ServerTransaction getNewServerTransaction(Request request) throws TransactionAlreadyExistsException, TransactionUnavailableException { + return null; + } + + @Override + public void sendRequest(Request request) throws SipException { + + } + + @Override + public void sendResponse(Response response) throws SipException { + + } + + @Override + public Dialog getNewDialog(Transaction transaction) throws SipException { + return null; + } + + @Override + public void setAutomaticDialogSupportEnabled(boolean b) { + + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RecordCacheManager.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RecordCacheManager.java new file mode 100644 index 00000000..9f1e9870 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RecordCacheManager.java @@ -0,0 +1,41 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.model.RecordList; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +@Component +public class RecordCacheManager { + private final ConcurrentHashMap recordMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap lockMap = new ConcurrentHashMap<>(); + + public void put(String key,RecordList list){ + recordMap.putIfAbsent(key, list); + } + + public RecordList get(String key){ + RecordList ret = recordMap.get(key); + if (ret == null) { + ret = new RecordList(); + recordMap.putIfAbsent(key, ret); + } + return ret; + } + + public void remove(String key) { + recordMap.remove(key); + lockMap.remove(key); + } + + public void addlock(String key){ + lockMap.put(key,new ReentrantLock()); + } + + public ReentrantLock getlock(String key){ + return lockMap.get(key); + } + + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ReqMsgHeaderBuilder.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ReqMsgHeaderBuilder.java new file mode 100644 index 00000000..a64d02c1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/ReqMsgHeaderBuilder.java @@ -0,0 +1,267 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.model.InviteInfo; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.service.IInviteService; +import com.fastbee.sip.service.ISipCacheService; +import com.fastbee.sip.util.SipUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import javax.sip.*; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.*; +import javax.sip.message.Request; +import java.text.ParseException; +import java.util.ArrayList; + +@Component +public class ReqMsgHeaderBuilder { + @Autowired + private SipFactory sipFactory; + + @Autowired + @Qualifier(value = "udpSipServer") + private SipProvider sipserver; + + @Autowired + private VideoSessionManager streamSession; + + @Autowired + private ISipCacheService sipCacheService; + + @Autowired + private IInviteService inviteService; + + /** + * 创建请求消息 + * + * @param device + * @param content + * @param fromTag + * @return + * @throws ParseException + * @throws InvalidArgumentException + * @throws PeerUnavailableException + */ + public Request createMessageRequest(SipDevice device, SipConfig sipConfig, String content, String fromTag) + throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + // sipuri + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceSipId(), + device.getHostaddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), + sipConfig.getPort(), device.getTransport(), SipUtil.getNewViaTag()); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getServerSipid(), + sipConfig.getIp() + ":" + sipConfig.getPort()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); + // to + SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceSipId(), + sipConfig.getDomain()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, SipUtil.getNewTag()); + // callid + CallIdHeader callIdHeader = sipserver.getNewCallId(); + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipCacheService.getCSEQ(sipConfig.getServerSipid()), Request.MESSAGE); + + request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, + fromHeader, toHeader, viaHeaders, maxForwards); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", + "MANSCDP+xml"); + request.setContent(content, contentTypeHeader); + + return request; + } + + public Request createInviteRequest(SipDevice device, SipConfig sipConfig, String channelId, String content, String ssrc, String fromTag) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + // 请求行 + SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostaddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), + device.getTransport(), SipUtil.getNewViaTag()); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getServerSipid(), + sipConfig.getDomain()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); // 必须要有标记,否则无法创建会话,无法回应ack + // to + SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, sipConfig.getDomain()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, null); + + // callid + CallIdHeader callIdHeader = sipserver.getNewCallId(); + + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipCacheService.getCSEQ(sipConfig.getServerSipid()), Request.INVITE); + request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader, + fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory() + .createSipURI(sipConfig.getServerSipid(), sipConfig.getIp() + ":" + sipConfig.getPort())); + + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + // Subject + SubjectHeader subjectHeader = sipFactory.createHeaderFactory() + .createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getIp(), 0)); + request.addHeader(subjectHeader); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", + "SDP"); + request.setContent(content, contentTypeHeader); + return request; + } + + public Request createPlaybackInviteRequest(SipDevice device, SipConfig sipConfig, String channelId, String content, String viaTag, String fromTag) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + // 请求行 + SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, + device.getHostaddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), + device.getTransport(), viaTag); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getServerSipid(), + sipConfig.getDomain()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); // 必须要有标记,否则无法创建会话,无法回应ack + // to + SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostaddress()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, null); + + // callid + CallIdHeader callIdHeader = sipserver.getNewCallId(); + + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipCacheService.getCSEQ(sipConfig.getServerSipid()), Request.INVITE); + request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader, + fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory() + .createSipURI(sipConfig.getServerSipid(), sipConfig.getIp() + ":" + sipConfig.getPort())); + + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", + "SDP"); + request.setContent(content, contentTypeHeader); + return request; + } + + public Request createByeRequest(SipDevice device, SipConfig sipConfig, String channelId, InviteInfo invite) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + //请求行 + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostaddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), + device.getTransport(), SipUtil.getNewViaTag()); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + //from + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getServerSipid(), sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, invite.getFromTag()); + //to + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostaddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, SipUtil.getNewTag()); + //Forwards + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); + //ceq + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipCacheService.getCSEQ(sipConfig.getServerSipid()), Request.BYE); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(invite.getCallId()); + request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory() + .createSipURI(sipConfig.getServerSipid(), sipConfig.getIp() + ":" + sipConfig.getPort())); + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + return request; + } + + public Request createRtspRequest(SipDevice device, SipConfig sipConfig, String channelId, String streamId, String content) + throws PeerUnavailableException, ParseException, InvalidArgumentException { + Request request = null; + VideoSessionInfo info = streamSession.getSessionInfo(device.getDeviceSipId(), channelId, streamId, null); + if (info == null) { + return null; + } + Dialog dialog = streamSession.getclientTransaction(info).getDialog(); + if (dialog == null) { + return null; + } + InviteInfo invite = inviteService.getInviteInfoBySSRC(info.getSsrc()); + if (invite == null) { + return null; + } + // 请求行 + SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostaddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(getLocalIp(sipConfig.getIp()), sipConfig.getPort(), + device.getTransport(), invite.getViaTag()); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getServerSipid(), + sipConfig.getDomain()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, invite.getFromTag()); + // to + SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostaddress()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, dialog.getRemoteTag()); + // callid + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(invite.getCallId());; + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipCacheService.getCSEQ(sipConfig.getServerSipid()), Request.INFO); + + request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader, + fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory() + .createSipURI(sipConfig.getServerSipid(), sipConfig.getIp() + ":" + sipConfig.getPort())); + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", + "MANSRTSP"); + request.setContent(content, contentTypeHeader); + return request; + } + + public String getLocalIp(String deviceLocalIp) { + if (!ObjectUtils.isEmpty(deviceLocalIp)) { + return deviceLocalIp; + } + return sipserver.getListeningPoint().getIPAddress(); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RequestBuilder.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RequestBuilder.java new file mode 100644 index 00000000..ccd9717b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/RequestBuilder.java @@ -0,0 +1,48 @@ +package com.fastbee.sip.server; + +import javax.sip.header.AuthorizationHeader; +import javax.sip.header.CallIdHeader; +import javax.sip.message.Request; + +public interface RequestBuilder { + + RequestBuilder requestLine(String sipId, String host, int port); + + RequestBuilder user(String user); + + RequestBuilder via(String host, int port, String transport, String viaTag); + + RequestBuilder from(String sipId, String domain, String fromTag); + + RequestBuilder to(String user, String domain, String toTag); + + RequestBuilder contact(String user, int port); + + RequestBuilder subject(String subject); + + RequestBuilder cSeq(int cSeq); + + RequestBuilder method(String method); + + RequestBuilder contentxml(byte[] content); + + RequestBuilder contentsdp(byte[] content); + + RequestBuilder authorization(AuthorizationHeader header); + + Request build(CallIdHeader callId); + + default RequestBuilder invite() { + return method(Request.INVITE); + } + + default RequestBuilder message() { + return method(Request.MESSAGE); + } + + default RequestBuilder subscribe() { + return method(Request.SUBSCRIBE); + } + +} + diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipLayer.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipLayer.java new file mode 100644 index 00000000..4065b5c4 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipLayer.java @@ -0,0 +1,93 @@ +package com.fastbee.sip.server; + +import com.fastbee.sip.conf.SysSipConfig; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.model.ZlmMediaServer; +import com.fastbee.sip.service.IMediaServerService; +import com.fastbee.sip.service.ISipCacheService; +import com.fastbee.sip.service.ISipConfigService; +import com.fastbee.sip.util.ZlmApiUtils; +import gov.nist.javax.sip.SipStackImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import javax.sip.*; +import java.util.Properties; + + +@Slf4j +@Configuration +public class SipLayer { + @Autowired + private SysSipConfig sipConfig; + + @Autowired + private IGBListener gbSIPListener; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Autowired + private ISipCacheService sipCacheService; + + @Autowired + private ISipConfigService sipConfigService; + + @Autowired + private IMediaServerService mediaServerService; + private SipStack sipStack; + private SipFactory sipFactory; + + + @Bean("sipFactory") + SipFactory createSipFactory() { + sipFactory = SipFactory.getInstance(); + sipFactory.setPathName("gov.nist"); + if (sipConfig.isEnabled()) { + //缓存zlm服务器配置 + MediaServer media = mediaServerService.selectMediaServerBytenantId(1L); + ZlmMediaServer mediaServer = zlmApiUtils.getMediaServerConfig(media); + if (mediaServer != null) { + log.info("zlm配置获取成功成功..."); + sipCacheService.updateMediaInfo(mediaServer); + mediaServerService.syncMediaServer(media, media.getSecret()); + } + } + return sipFactory; + } + + @Bean("sipStack") + @DependsOn("sipFactory") + SipStack createSipStack() throws PeerUnavailableException { + Properties properties = new Properties(); + properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); + properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getIp()); + properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false"); + properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0"); + properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log"); + properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log"); + sipStack = (SipStackImpl) sipFactory.createSipStack(properties); + return sipStack; + } + + @Bean("udpSipServer") + @DependsOn("sipStack") + SipProvider startUdpListener() throws Exception { + if (sipConfig.isEnabled()) { + log.info("startUdpListener"); + ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getIp(), sipConfig.getPort(), "UDP"); + SipProvider udpSipProvider = sipStack.createSipProvider(udpListeningPoint); + udpSipProvider.addSipListener(gbSIPListener); + log.info("Sip Server UDP 启动成功 port {}", sipConfig.getPort()); + sipConfigService.syncSipConfig(sipConfig); + return udpSipProvider; + } else { + return new NullSipProvider(); + } + + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipMessage.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipMessage.java new file mode 100644 index 00000000..b67d0143 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/SipMessage.java @@ -0,0 +1,15 @@ +package com.fastbee.sip.server; + +public interface SipMessage { + String getDeviceId(); + + String getSn(); + + default int totalPart() { + return 1; + } + + default int numberOfPart() { + return 1; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/VideoSessionManager.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/VideoSessionManager.java new file mode 100644 index 00000000..3ac13978 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/VideoSessionManager.java @@ -0,0 +1,195 @@ +package com.fastbee.sip.server; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.sip.enums.SessionType; +import com.fastbee.sip.model.InviteInfo; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.util.SipUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import javax.sip.ClientTransaction; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Collections.emptyList; + +@Component +public class VideoSessionManager { + @Autowired + private RedisCache redisCache; + + private final ConcurrentHashMap sessionMap = new ConcurrentHashMap<>(); + + public String createPlaySsrc(String domain) { + return SipUtil.getPlaySsrc(domain); + } + + public String createPlayBackSsrc(String domain) { + return SipUtil.getPlayBackSsrc(domain); + } + + public void put(VideoSessionInfo info, ClientTransaction client) { + String ssrc = info.getSsrc(); + if (info.getType() == SessionType.play || info.getType() == SessionType.playrecord) { + ssrc = info.getType().name(); + } + String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), ssrc); + redisCache.setCacheObject(key, info); + if (!ObjectUtils.isEmpty(client)) { + key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), info.getSsrc()); + sessionMap.put(key, client); + } + } + + public ClientTransaction getclientTransaction(VideoSessionInfo info) { + String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), info.getSsrc()); + return sessionMap.get(key); + } + + public ClientTransaction getclientTransaction(VideoSessionInfo info, InviteInfo invite) { + String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), invite.getSsrc()); + return sessionMap.get(key); + } + + public VideoSessionInfo getSessionInfo(String deviceId, String channelId, String stream, String callId) { + if (ObjectUtils.isEmpty(deviceId)) { + deviceId = "*"; + } + if (ObjectUtils.isEmpty(channelId)) { + channelId = "*"; + } + if (ObjectUtils.isEmpty(stream)) { + stream = "*"; + } + if (ObjectUtils.isEmpty(callId)) { + callId = "*"; + } + String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId); + List scanResult = redisCache.scan(key); + if (scanResult.size() == 0) { + return null; + } + return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0)); + } + + public VideoSessionInfo getSessionInfoByCallId(String callId) { + if (ObjectUtils.isEmpty(callId)) { + return null; + } + String key = RedisKeyBuilder.buildStreamCacheKey("*", "*", "*", callId); + List scanResult = redisCache.scan(key); + if (!scanResult.isEmpty()) { + return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0)); + } else { + return null; + } + } + + public VideoSessionInfo getSessionInfoBySSRC(String SSRC) { + if (ObjectUtils.isEmpty(SSRC)) { + return null; + } + String key = RedisKeyBuilder.buildStreamCacheKey("*", "*", SSRC, "*"); + List scanResult = redisCache.scan(key); + if (!scanResult.isEmpty()) { + return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0)); + } else { + return null; + } + + } + + public List getSessionInfoForAll(String deviceId, String channelId, String stream, String callId) { + if (ObjectUtils.isEmpty(deviceId)) { + deviceId = "*"; + } + if (ObjectUtils.isEmpty(channelId)) { + channelId = "*"; + } + if (ObjectUtils.isEmpty(stream)) { + stream = "*"; + } + if (ObjectUtils.isEmpty(callId)) { + callId = "*"; + } + String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId); + List scanResult = redisCache.scan(key); + if (scanResult.size() == 0) { + return emptyList(); + } + List result = new ArrayList<>(); + for (Object keyObj : scanResult) { + result.add((VideoSessionInfo) redisCache.getCacheObject((String) keyObj)); + } + return result; + } + + public String getMediaServerId(String deviceId, String channelId, String stream) { + VideoSessionInfo ssrcTransaction = getSessionInfo(deviceId, channelId, null, stream); + if (ssrcTransaction == null) { + return null; + } + return ssrcTransaction.getMediaServerId(); + } + + public String getSSRC(String deviceId, String channelId, String stream) { + VideoSessionInfo ssrcTransaction = getSessionInfo(deviceId, channelId, null, stream); + if (ssrcTransaction == null) { + return null; + } + return ssrcTransaction.getSsrc(); + } + + public void remove(String deviceId, String channelId, String stream, String callId) { + String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId); + if (!Objects.equals(callId, "play")) { + redisCache.deleteObject(key); + } + sessionMap.remove(key); + } + + public void remove(String deviceId, String channelId, String stream) { + List sinfoList = getSessionInfoForAll(deviceId, channelId, stream, null); + if (sinfoList == null || sinfoList.isEmpty()) { + return; + } + for (VideoSessionInfo sinfo : sinfoList) { + String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, sinfo.getSsrc()); + if (sinfo.getType() != SessionType.play) { + redisCache.deleteObject(key); + } + sessionMap.remove(key); + } + } + + public void removeByCallId(String deviceId, String channelId, String callId) { + VideoSessionInfo sinfo = getSessionInfo(deviceId, channelId, null, callId); + if (sinfo == null) { + return; + } + String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, sinfo.getStream(), sinfo.getSsrc()); + if (sinfo.getType() != SessionType.play) { + redisCache.deleteObject(key); + } + sessionMap.remove(key); + } + + public List getAllSsrc() { + String allkey = RedisKeyBuilder.buildStreamCacheKey("*", "*", "*", "*"); + List scanResult = redisCache.scan(allkey); + if (scanResult.size() == 0) { + return null; + } + List result = new ArrayList<>(); + for (Object ssrcTransactionKey : scanResult) { + String key = (String) ssrcTransactionKey; + result.add((VideoSessionInfo) redisCache.getCacheObject((String) key)); + } + return result; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/GBListenerImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/GBListenerImpl.java new file mode 100644 index 00000000..23f8b2bc --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/GBListenerImpl.java @@ -0,0 +1,90 @@ +package com.fastbee.sip.server.impl; + +import com.fastbee.sip.handler.IReqHandler; +import com.fastbee.sip.handler.IResHandler; +import com.fastbee.sip.server.IGBListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.sip.*; +import javax.sip.header.CSeqHeader; +import javax.sip.message.Response; +import java.text.ParseException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class GBListenerImpl implements IGBListener { + + private static final Map requestProcessorMap = new ConcurrentHashMap<>(); + private static final Map responseProcessorMap = new ConcurrentHashMap<>(); + + public void addRequestProcessor(String method, IReqHandler processor) { + requestProcessorMap.put(method, processor); + } + + public void addResponseProcessor(String method, IResHandler processor) { + responseProcessorMap.put(method, processor); + } + + @Override + @Async("taskExecutor") + public void processRequest(RequestEvent evt) { + String method = evt.getRequest().getMethod(); + IReqHandler sipRequestProcessor = requestProcessorMap.get(method); + if (sipRequestProcessor == null) { + log.warn("不支持方法{}的request", method); + return; + } + requestProcessorMap.get(method).processMsg(evt); + } + + @Override + @Async("taskExecutor") + public void processResponse(ResponseEvent evt) { + //处理响应消息 + Response response = evt.getResponse(); + int status = response.getStatusCode(); + // 响应成功 + if ((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) { + log.info("response:{},",response.getReasonPhrase()); + CSeqHeader cseqHeader = (CSeqHeader) evt.getResponse().getHeader(CSeqHeader.NAME); + String method = cseqHeader.getMethod(); + log.info("接收response响应!status:{},message:{},method:{}",status,response.getReasonPhrase(),method); + IResHandler sipRequestProcessor = responseProcessorMap.get(method); + if (sipRequestProcessor != null) { + try { + sipRequestProcessor.processMsg(evt); + } catch (ParseException e) { + e.printStackTrace(); + } + } + } else if ((status >= Response.TRYING) && (status < Response.OK)) { + log.info("接收response响应!status:{},,message:{}",status,response.getReasonPhrase()); + } else { + log.warn("接收到失败的response响应!status:{},,message:{}",status,response.getReasonPhrase()); + } + } + + @Override + public void processTimeout(TimeoutEvent timeoutEvent) { + log.info("processTimeout:{}", timeoutEvent); + } + + @Override + public void processIOException(IOExceptionEvent ioExceptionEvent) { + //log.debug("processIOException:{}", ioExceptionEvent); + } + + @Override + public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { + //log.debug("processTransactionTerminated:{}", transactionTerminatedEvent); + } + + @Override + public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { + //log.debug("processDialogTerminated:{}", dialogTerminatedEvent); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/MessageInvokerImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/MessageInvokerImpl.java new file mode 100644 index 00000000..bf2d258e --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/MessageInvokerImpl.java @@ -0,0 +1,317 @@ +package com.fastbee.sip.server.impl; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.server.SipMessage; +import com.fastbee.sip.server.msg.*; +import com.fastbee.sip.service.ISipConfigService; +import com.fastbee.sip.util.SipUtil; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.sip.*; +import javax.sip.header.EventHeader; +import javax.sip.header.ExpiresHeader; +import javax.sip.header.Header; +import javax.sip.message.Request; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +@Slf4j +@Component +public class MessageInvokerImpl implements MessageInvoker { + @Autowired + private SipFactory sipFactory; + @Autowired + private ISipConfigService sipConfigService; + + @Autowired + private RedisCache redisCache; + + @Autowired + @Qualifier(value = "udpSipServer") + private SipProvider sipserver; + + public final static XmlMapper mapper = new XmlMapper(); + + private void sendRequest(Function bodyBuilder, + String requestMethod, + SipDevice device, + String viaTag, + String fromTag, + String toTag, + Header... headers) { + int id = (int) ((Math.random() * 9 + 1) * 100000); + createTransaction(requestMethod, bodyBuilder.apply(id), device.getDeviceSipId(), device, viaTag, fromTag, toTag, headers); + } + + private void sendRequest(String xml, + String requestMethod, + SipDevice device, + String viaTag, + String fromTag, + String toTag, + Header... headers) { + createTransaction(requestMethod, xml, device.getDeviceSipId(), device, viaTag, fromTag, toTag, headers); + } + @SneakyThrows + private ClientTransaction createTransaction(String method, + String xml, + String deviceId, + SipDevice device, + String viaTag, + String fromTag, + String toTag, + Header... headers) { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + Request request = new RequestBuilderImpl(sipFactory) + .requestLine(deviceId, device.getIp(), device.getPort()) + .method(method) + .user(device.getDeviceSipId()) + .via(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag) + .from(sipConfig.getServerSipid(), sipConfig.getIp() + ":" + sipConfig.getPort(), fromTag) + .to(deviceId, device.getHostAndPort(), toTag) + .contentxml(xml.getBytes("GB2312")) + .contact(sipConfig.getServerSipid(), sipConfig.getPort()) + .build(sipserver.getNewCallId()); + log.debug("prepare SIP request \n{}", request); + for (Header header : headers) { + request.addHeader(header); + } + return transmitRequest(request); + } + + static Map> valueConverter = new ConcurrentHashMap<>(); + + static { + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); + mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true); + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + mapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); + valueConverter.put("DeviceInfo", xml -> jsonNodeToObject(xml, GB28181Device.class)); + valueConverter.put("Keepalive", xml -> jsonNodeToObject(xml, KeepaliveMessage.class)); + valueConverter.put("Catalog", xml -> jsonNodeToObject(xml, CatalogInfo.class)); + valueConverter.put("Alarm", xml -> jsonNodeToObject(xml, Alarm.class)); + valueConverter.put("ConfigDownload", xml -> jsonNodeToObject(xml, ConfigDownload.class)); + valueConverter.put("DeviceControl", xml -> jsonNodeToObject(xml, DeviceControl.class)); + + } + + @SneakyThrows + private static T jsonNodeToObject(String xml, Class tClass) { + return mapper.readValue(xml.replace("\r\n", "").replace("\n", ""), tClass); + } + + @SneakyThrows + public SipMessage messageToObj(RequestEvent event) { + try { + Request request = event.getRequest(); + if ((!Request.MESSAGE.equals(request.getMethod()) + && !Request.NOTIFY.equals(request.getMethod()) + && !Request.SUBSCRIBE.equals(request.getMethod())) + || request.getRawContent() == null) { + return null; + } + String content = new String(request.getRawContent(), "GB2312"); + JsonNode jsonNode = mapper.readTree(content); + String cmdType = jsonNode.get("CmdType").asText(); + Function converter = valueConverter.get(cmdType); + if (null != converter) { + return converter.apply(content); + } + } catch (Throwable error) { + log.error("handle SIP message error \n{}", event.getRequest(), error); + } + return null; + } + + private ClientTransaction transmitRequest(Request request) throws SipException { + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } + + @Override + @SneakyThrows + public void subscribe(@Nonnull SipDevice device, int startAlarmPriority, int endAlarmPriority, int expires, @Nonnull String alarmMethod, @Nullable Date startAlarmTime, @Nullable Date endAlarmTime) { + String startAlarmTimeString = startAlarmTime == null ? "" : SipUtil.dateToISO8601(startAlarmTime); + String endAlarmTimeString = endAlarmTime == null ? "" : SipUtil.dateToISO8601(endAlarmTime); + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(expires); + EventHeader eventHeader = sipFactory.createHeaderFactory().createEventHeader("presemce"); + this.sendRequest(sn -> + "\r\n" + + "\r\n" + + "Alarm\r\n" + + "" + sn + "\r\n" + + "" + device.getDeviceSipId() + "\r\n" + + "" + startAlarmPriority + "\n" + + "" + endAlarmPriority + "\n" + + "" + alarmMethod + "\n" + + "" + startAlarmTimeString + "\n" + + "" + endAlarmTimeString + "\n" + + "\r\n", + Request.SUBSCRIBE, + device, + null, + "AK32B1U8DKDrA", + null, + expiresHeader, + eventHeader + ); + } + + @Override + @SneakyThrows + public void subscribeCatalog(SipDevice device, Date from, Date to) { + String fromTimeString = SipUtil.dateToISO8601(from); + String toTimeString = SipUtil.dateToISO8601(to); + ; + int expires = (int) ((to.getTime() - from.getTime()) / 1000); + + ExpiresHeader header = sipFactory.createHeaderFactory().createExpiresHeader(expires); + this.sendRequest(sn -> + "\r\n" + + "\r\n" + + "Catalog\r\n" + + "" + sn + "\r\n" + + "" + device.getDeviceSipId() + "\r\n" + + "" + fromTimeString + "\r\n" + + "" + toTimeString + "\r\n" + + "\r\n", + Request.SUBSCRIBE, + device, + "ViaSubscribeCatalog", + "SubscribeCatalogTag", + null, + header + ); + } + + @Override + public void deviceControl(SipDevice device, DeviceControl command) { + this.sendRequest(sn -> command.toXml(sn, "GB2312"), + Request.MESSAGE, + device, + "ViaPtzBranch", + "FromPtzTag", + null + ); + } + + + @Override + public boolean deviceInfoQuery(SipDevice device) { + this.sendRequest(sn -> + "\r\n" + + "\r\n" + + "DeviceInfo\r\n" + + "" + sn + "\r\n" + + "" + device.getDeviceSipId() + "\r\n" + + "\r\n", + Request.MESSAGE, + device, + "ViaDeviceInfoBranch", + "FromDeviceInfoTag", + "ToDeviceInfoTag" + ); + return true; + } + + @Override + public boolean catalogQuery(SipDevice device) { + this.sendRequest(sn -> + "\r\n" + + "\r\n" + + "Catalog\r\n" + + "" + sn + "\r\n" + + "" + device.getDeviceSipId() + "\r\n" + + "\r\n", + Request.MESSAGE, + device, + "ViaCatalogBranch", + "FromCatalogTag", + null + ); + return true; + } + + @Override + public boolean recordInfoQuery(SipDevice device, String sn, String channelId, Date start, Date end) { + String startTimeString = SipUtil.dateToISO8601(start); + String endTimeString = SipUtil.dateToISO8601(end); + ; + this.sendRequest("\r\n" + + "\r\n" + + "RecordInfo\r\n" + + "" + sn + "\r\n" + + "" + channelId + "\r\n" + + "" + startTimeString + "\r\n" + + "" + endTimeString + "\r\n" + + "0\r\n" + + "all\r\n" + + "\r\n", + Request.MESSAGE, + device, + "ViarecordInfoBranch", + "FromrecordInfoTag", + null + ); + return true; + } + + @Override + public ConfigDownload downloadConfig(SipDevice device, ConfigDownload.ConfigType... configType) { + return null; + } + + @Override + public ClientTransaction request(SipDevice device, Request request, boolean awaitAck) { + return null; + } + + @Override + public Object request(ClientTransaction transaction, Request request, boolean awaitAck) { + return null; + } + + public T getExecResult(String key, long timeout) { + long time = 0; + while (true) { + try { + T instance = redisCache.getCacheObject(key); + if (null == instance) { + if (time >= timeout) { + log.error("key:{} get Response timeout", key); + return null; + } + time += 1000; + TimeUnit.MILLISECONDS.sleep(1000L); + } else { + return instance; + } + } catch (Exception e) { + log.error("", e); + Thread.currentThread().interrupt(); + break; + } + } + log.error("key:{} can't get Response", key); + return null; + } +} + diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RequestBuilderImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RequestBuilderImpl.java new file mode 100644 index 00000000..47272cf6 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RequestBuilderImpl.java @@ -0,0 +1,186 @@ +package com.fastbee.sip.server.impl; + +import com.fastbee.sip.server.RequestBuilder; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; + +import javax.sip.SipFactory; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.*; +import javax.sip.message.Request; +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class RequestBuilderImpl implements RequestBuilder { + private SipFactory sipFactory; + private String host; + private int port; + private String user; + private String method; + private boolean transportTcp; + private long cSeq = 1L; + private final List viaHeaders = new ArrayList<>(); + + private ContactHeader contactHeader; + private SubjectHeader subjectHeader; + private FromHeader fromHeader; + private ToHeader toHeader; + private AuthorizationHeader authorizationHeader; + private ContentTypeHeader contentTypeHeader; + private byte[] content; + private SipURI requestLine; + + public RequestBuilderImpl(SipFactory sipFactory) { + this.sipFactory = sipFactory; + } + + @Override + @SneakyThrows + public RequestBuilder requestLine(String sipId, String host, int port) { + requestLine = sipFactory.createAddressFactory().createSipURI(sipId, host + ":" + port); + return this; + } + + @Override + public RequestBuilder user(String user) { + this.user = user; + return this; + } + + @Override + public RequestBuilder method(String method) { + this.method = method; + return this; + } + + @Override + @SneakyThrows + public RequestBuilder via(String host, + int port, + String transport, + String viaTag) { + this.port = port; + this.transportTcp = "TCP".equals(transport); + this.host = host; + ViaHeader viaHeader = sipFactory.createHeaderFactory() + .createViaHeader(host, port, transport, viaTag); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + return this; + } + + @Override + @SneakyThrows + public RequestBuilder from(String sipId, + String domain, + String fromTag) { + SipURI from = sipFactory.createAddressFactory().createSipURI(sipId, domain); + Address fromAddress = sipFactory.createAddressFactory().createAddress(from); + fromHeader = sipFactory.createHeaderFactory() + .createFromHeader(fromAddress, fromTag); + return this; + } + + @Override + @SneakyThrows + public RequestBuilder to(String sipId, + String domain, + String toTag) { + SipURI from = sipFactory.createAddressFactory().createSipURI(sipId, domain); + Address fromAddress = sipFactory.createAddressFactory().createAddress(from); + toHeader = sipFactory.createHeaderFactory() + .createToHeader(fromAddress, toTag); + + return this; + } + + @Override + @SneakyThrows + public RequestBuilder contact(String user, int port) { + Address concatAddress = sipFactory.createAddressFactory() + .createAddress(sipFactory.createAddressFactory() + .createSipURI(user, user + ":" + port)); + contactHeader = sipFactory.createHeaderFactory().createContactHeader(concatAddress); + return this; + } + + @Override + public RequestBuilder cSeq(int cSeq) { + this.cSeq = cSeq; + return this; + } + + @Override + @SneakyThrows + public RequestBuilder subject(String subject) { + subjectHeader = sipFactory.createHeaderFactory() + .createSubjectHeader(subject); + + return this; + } + + @Override + @SneakyThrows + public RequestBuilder contentxml(byte[] content) { + contentTypeHeader = sipFactory.createHeaderFactory() + .createContentTypeHeader("APPLICATION", "MANSCDP+xml"); + this.content = content; + return this; + } + + @Override + @SneakyThrows + public RequestBuilder contentsdp(byte[] content) { + contentTypeHeader = sipFactory.createHeaderFactory() + .createContentTypeHeader("APPLICATION", "SDP"); + this.content = content; + return this; + } + + @Override + public RequestBuilder authorization(AuthorizationHeader header) { + this.authorizationHeader = header; + return this; + } + + @Override + @SneakyThrows + public Request build(CallIdHeader callId) { + //请求行 + SipURI requestLine = this.requestLine == null ? sipFactory.createAddressFactory() + .createSipURI(user, host + ":" + port) + : this.requestLine; + + //callid + CallIdHeader callIdHeader = callId; + + //Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + + //ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(cSeq, method); + Request request = sipFactory.createMessageFactory() + .createRequest(requestLine, method, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); + //Authorization + if (this.authorizationHeader != null) { + request.addHeader(this.authorizationHeader); + } + //Contact + if (contactHeader != null) { + request.addHeader(contactHeader); + } + // Subject + if (subjectHeader != null) { + request.addHeader(subjectHeader); + } + //Content + if (content != null) { + request.setContent(content, contentTypeHeader); + } + + return request; + + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RtspCmdImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RtspCmdImpl.java new file mode 100644 index 00000000..c60e6ebc --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/RtspCmdImpl.java @@ -0,0 +1,122 @@ +package com.fastbee.sip.server.impl; + +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.server.IRtspCmd; +import com.fastbee.sip.server.ReqMsgHeaderBuilder; +import com.fastbee.sip.service.ISipConfigService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.sip.ClientTransaction; +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import javax.sip.SipProvider; +import javax.sip.message.Request; +import java.text.ParseException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class RtspCmdImpl implements IRtspCmd { + public static Map CSEQCACHE = new ConcurrentHashMap<>(); + + @Autowired + private ReqMsgHeaderBuilder headerBuilder; + + @Autowired + @Qualifier(value = "udpSipServer") + private SipProvider sipserver; + + @Autowired + private ISipConfigService sipConfigService; + + public void playPause(SipDevice device, String channelId, String streamId) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("[playPause] sipConfig is null"); + return ; + } + String content = "PAUSE RTSP/1.0\r\n" + + "CSeq: " + getInfoCseq() + "\r\n" + + "PauseTime: now\r\n"; + Request request = headerBuilder.createRtspRequest(device, sipConfig, channelId, streamId, content); + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + public void playReplay(SipDevice device, String channelId, String streamId) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("[playReplay] sipConfig is null"); + return ; + } + String content = "PLAY RTSP/1.0\r\n" + + "CSeq: " + getInfoCseq() + "\r\n" + + "Range: npt=now-\r\n"; + Request request = headerBuilder.createRtspRequest(device, sipConfig, channelId, streamId, content); + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + public void playBackSeek(SipDevice device, String channelId, String streamId, long seektime) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("[playBackSeek] sipConfig is null"); + return ; + } + String content = "PLAY RTSP/1.0\r\n" + + "CSeq: " + getInfoCseq() + "\r\n" + + "Range: npt=" + Math.abs(seektime) + "-\r\n"; + Request request = headerBuilder.createRtspRequest(device, sipConfig, channelId, streamId, content); + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + public void playBackSpeed(SipDevice device, String channelId, String streamId, Integer speed) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("[playBackSpeed] sipConfig is null"); + return ; + } + String content = "PLAY RTSP/1.0\r\n" + + "CSeq: " + getInfoCseq() + "\r\n" + + "Scale: " + speed + ".000000\r\n"; + Request request = headerBuilder.createRtspRequest(device, sipConfig, channelId, streamId, content); + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + + } + + public void setCseq(String streamId) { + if (CSEQCACHE.containsKey(streamId)) { + CSEQCACHE.put(streamId, CSEQCACHE.get(streamId) + 1); + } else { + CSEQCACHE.put(streamId, 2l); + } + } + + private int getInfoCseq() { + return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8)); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/SipCmdImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/SipCmdImpl.java new file mode 100644 index 00000000..97a381e3 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/impl/SipCmdImpl.java @@ -0,0 +1,381 @@ +package com.fastbee.sip.server.impl; + +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.enums.SessionType; +import com.fastbee.sip.model.InviteInfo; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.server.ISipCmd; +import com.fastbee.sip.server.ReqMsgHeaderBuilder; +import com.fastbee.sip.server.VideoSessionManager; +import com.fastbee.sip.service.IInviteService; +import com.fastbee.sip.service.IMediaServerService; +import com.fastbee.sip.service.ISipConfigService; +import com.fastbee.sip.service.ISipDeviceService; +import com.fastbee.sip.util.SipUtil; +import com.fastbee.sip.util.ZlmApiUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.sip.*; +import javax.sip.message.Request; +import java.text.ParseException; +import java.util.List; + + +@Slf4j +@Component +public class SipCmdImpl implements ISipCmd { + + @Autowired + private VideoSessionManager streamSession; + + @Autowired + private ReqMsgHeaderBuilder headerBuilder; + + @Autowired + private ISipConfigService sipConfigService; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private ISipDeviceService sipDeviceService; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Autowired + private VideoSessionManager videoSessionManager; + + @Autowired + private IInviteService inviteService; + + @Autowired + @Qualifier(value = "udpSipServer") + private SipProvider sipserver; + + @Override + public VideoSessionInfo playStreamCmd(SipDevice device, String channelId, boolean record) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("playStreamCmd sipConfig is null"); + return null; + } + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(device.getDeviceSipId()); + if (mediaInfo == null) { + log.error("playStreamCmd mediaInfo is null"); + return null; + } + VideoSessionInfo info = VideoSessionInfo.builder() + .mediaServerId(mediaInfo.getServerId()) + .deviceId(device.getDeviceSipId()) + .channelId(channelId) + .streamMode(device.getStreammode().toUpperCase()) + .build(); + String fromTag; + if (record) { + info.setType(SessionType.playrecord); + fromTag = "playrecord"; + } else { + info.setType(SessionType.play); + fromTag = "play"; + } + //创建rtp服务器 + info = mediaServerService.createRTPServer(sipConfig, mediaInfo, device, info); + //创建Invite会话 + String content = buildRequestContent(sipConfig, mediaInfo, info); + Request request = headerBuilder.createInviteRequest(device, sipConfig, channelId, content, info.getSsrc(), fromTag); + //发送消息 + ClientTransaction transaction = transmitRequest(request); + log.info("playStreamCmd streamSession: {}", info); + InviteInfo invite = InviteInfo.builder() + .ssrc(info.getSsrc()) + .fromTag(fromTag) + .callId(transaction.getDialog().getCallId().getCallId()) + .port(info.getPort()).build(); + log.warn("playStreamCmd invite: {}", invite); + inviteService.updateInviteInfo(info, invite); + streamSession.put(info, transaction); + return info; + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public VideoSessionInfo playbackStreamCmd(SipDevice device, String channelId, String startTime, String endTime) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("playbackStreamCmd sipConfig is null"); + return null; + } + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(device.getDeviceSipId()); + if (mediaInfo == null) { + log.error("playbackStreamCmd mediaInfo is null"); + return null; + } + VideoSessionInfo info = VideoSessionInfo.builder() + .mediaServerId(mediaInfo.getServerId()) + .deviceId(device.getDeviceSipId()) + .channelId(channelId) + .streamMode(device.getStreammode().toUpperCase()) + .type(SessionType.playback) + .startTime(startTime) + .endTime(endTime) + .build(); + //创建rtp服务器 + info = mediaServerService.createRTPServer(sipConfig, mediaInfo, device, info); + //创建Invite会话 + String fromTag = "playback" + SipUtil.getNewFromTag(); + String viaTag = SipUtil.getNewViaTag(); + String content = buildRequestContent(sipConfig, mediaInfo, info); + Request request = headerBuilder.createPlaybackInviteRequest(device, sipConfig, channelId, content, viaTag, fromTag); + //发送消息 + ClientTransaction transaction = transmitRequest(request); + log.info("playbackStreamCmd streamSession: {}", info); + InviteInfo invite = InviteInfo.builder() + .ssrc(info.getSsrc()) + .fromTag(fromTag) + .viaTag(viaTag) + .callId(transaction.getDialog().getCallId().getCallId()) + .port(info.getPort()).build(); + log.warn("playbackStreamCmd invite: {}", invite); + inviteService.updateInviteInfo(info, invite); + streamSession.put(info, transaction); + return info; + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public VideoSessionInfo downloadStreamCmd(SipDevice device, String channelId, + String startTime, String endTime, int downloadSpeed) { + try { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("downloadStreamCmd sipConfig is null"); + return null; + } + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(device.getDeviceSipId()); + if (mediaInfo == null) { + log.error("downloadStreamCmd mediaInfo is null"); + return null; + } + VideoSessionInfo info = VideoSessionInfo.builder() + .mediaServerId(mediaInfo.getServerId()) + .deviceId(device.getDeviceSipId()) + .channelId(channelId) + .streamMode(device.getStreammode().toUpperCase()) + .type(SessionType.download) + .startTime(startTime) + .endTime(endTime) + .downloadSpeed(downloadSpeed) + .build(); + ; + //创建rtp服务器 + info = mediaServerService.createRTPServer(sipConfig, mediaInfo, device, info); + //创建Invite会话 + String fromTag = "download" + SipUtil.getNewFromTag();; + String viaTag = SipUtil.getNewViaTag(); + String content = buildRequestContent(sipConfig, mediaInfo, info); + Request request = headerBuilder.createPlaybackInviteRequest(device, sipConfig, channelId, content, viaTag, fromTag); + //发送消息 + ClientTransaction transaction = transmitRequest(request); + log.info("downloadStreamCmd streamSession: {}", info); + InviteInfo invite = InviteInfo.builder() + .ssrc(info.getSsrc()) + .fromTag(fromTag) + .viaTag(viaTag) + .callId(transaction.getDialog().getCallId().getCallId()) + .port(info.getPort()).build(); + log.warn("downloadStreamCmd invite: {}", invite); + inviteService.updateInviteInfo(info, invite); + streamSession.put(info, transaction); + return info; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void streamByeCmd(SipDevice device, String channelId, String stream, String ssrc) { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (sipConfig == null) { + log.error("[发送BYE] sipConfig is null"); + return; + } + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(device.getDeviceSipId()); + if (mediaInfo == null) { + log.error("[发送BYE] mediaInfo is null"); + return; + } + List SessionInfoList = streamSession.getSessionInfoForAll(device.getDeviceSipId(), channelId, stream, ssrc); + if (SessionInfoList == null || SessionInfoList.isEmpty()) { + log.warn("[发送BYE] 未找到事务信息,设备: device: {}, channel: {}", device.getDeviceSipId(), channelId); + return; + } + for (VideoSessionInfo info : SessionInfoList) { + try { + log.warn("[发送BYE] 设备: device: {}, channel: {}, stream: {}, ssrc: {}", device.getDeviceSipId(), + info.getChannelId(), info.getStream(), info.getSsrc()); + List list = inviteService.getInviteInfoAll(info.getType(), info.getDeviceId(), info.getChannelId(), info.getStream()); + if (list.isEmpty()) { + log.warn("[发送BYE] 未找到invite信息,设备: Stream: {}", info.getStream()); + } else { + for (InviteInfo invite : list) { + // 发送bye消息 + Request request = headerBuilder.createByeRequest(device, sipConfig, channelId, invite); + //获取缓存会话 + ClientTransaction transaction = videoSessionManager.getclientTransaction(info); + if (transaction == null) { + log.warn("[发送BYE] transaction is null"); + continue; + } + Dialog dialog = transaction.getDialog(); + if (dialog == null) { + log.warn("[发送BYE] transaction is dialog"); + continue; + } + //创建客户端,发送请求 + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + dialog.sendRequest(clientTransaction); + // 释放ssrc + SipUtil.releaseSsrc(info.getSsrc()); + // 关闭rtp服务器 + zlmApiUtils.closeRTPServer(mediaInfo, stream); + log.warn("closeRTPServer Port:{}", info.getPort()); + if (info.isPushing()) { + info.setPushing(false); + } + if (info.isRecording()) { + info.setPushing(false); + } + streamSession.put(info, null); + // 删除会话缓存 + streamSession.remove(info.getDeviceId(), info.getChannelId(), stream, info.getSsrc()); + // 删除invite缓存 + inviteService.removeInviteInfo(info.getType(), info.getDeviceId(), info.getChannelId(), info.getStream()); + } + } + } catch (ParseException | SipException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + } + + @Override + public void streamByeCmd(String deviceId, String channelId, String stream, String ssrc) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + if (dev == null) { + log.error("[发送BYE] device is null"); + return; + } + streamByeCmd(dev, channelId, stream, ssrc); + } + + private ClientTransaction transmitRequest(Request request) throws SipException { + log.info("transmitRequest:{}", request); + ClientTransaction clientTransaction = sipserver.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } + + private String buildRequestContent(SipConfig sipConfig, MediaServer mediaInfo, VideoSessionInfo info) { + String streamMode = info.getStreamMode(); + StringBuilder content = new StringBuilder(200); + content.append("v=0\r\n"); + switch (info.getType()) { + case play: + content.append("o=").append(info.getChannelId()).append(" 0 0 IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("t=0 0\r\n"); + break; + case playrecord: + content.append("o=").append(info.getChannelId()).append(" 0 0 IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("t=0 0\r\n"); + break; + case playback: + content.append("o=").append(info.getChannelId()).append(" 0 0 IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("s=Playback\r\n"); + content.append("u=").append(info.getChannelId()).append(":0\r\n"); + content.append("c=IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("t=").append(info.getStartTime()).append(" ").append(info.getEndTime()).append("\r\n"); + break; + case download: + content.append("o=").append(info.getChannelId()).append(" 0 0 IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("s=Download\r\n"); + content.append("u=").append(info.getChannelId()).append(":0\r\n"); + content.append("c=IN IP4 ").append(mediaInfo.getIp()).append("\r\n"); + content.append("t=").append(info.getStartTime()).append(" ").append(info.getEndTime()).append("\r\n"); + break; + } + if (sipConfig.getSeniorsdp() != null && sipConfig.getSeniorsdp() == 1) { + if ("TCP-PASSIVE".equals(streamMode)) { + content.append("m=video ").append(info.getPort()).append(" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); + } else if ("TCP-ACTIVE".equals(streamMode)) { + content.append("m=video ").append(info.getPort()).append(" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); + } else if ("UDP".equals(streamMode)) { + content.append("m=video ").append(info.getPort()).append(" RTP/AVP 96 126 125 99 34 98 97\r\n"); + } + content.append("a=recvonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + content.append("a=fmtp:126 profile-level-id=42e01e\r\n"); + content.append("a=rtpmap:126 H264/90000\r\n"); + content.append("a=rtpmap:125 H264S/90000\r\n"); + content.append("a=fmtp:125 profile-level-id=42e01e\r\n"); + content.append("a=rtpmap:99 MP4V-ES/90000\r\n"); + content.append("a=fmtp:99 profile-level-id=3\r\n"); + content.append("a=rtpmap:98 H264/90000\r\n"); + content.append("a=rtpmap:97 MPEG4/90000\r\n"); + if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式 + content.append("a=setup:passive\r\n"); + content.append("a=connection:new\r\n"); + } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 + content.append("a=setup:active\r\n"); + content.append("a=connection:new\r\n"); + } + } else { + switch (streamMode) { + case "TCP-PASSIVE": + content.append("m=video ").append(info.getPort()).append(" TCP/RTP/AVP 96 97 98 99\r\n"); + break; + case "TCP-ACTIVE": + content.append("m=video ").append(info.getPort()).append(" TCP/RTP/AVP 96 97 98 99\r\n"); + break; + case "UDP": + //content.append("m=video ").append(info.getPort()).append(" RTP/AVP 96 97 98 99\r\n"); + content.append("m=video ").append(info.getPort()).append(" RTP/AVP 96 97 98\r\n"); + break; + } + content.append("a=recvonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + content.append("a=rtpmap:97 MPEG4/90000\r\n"); + content.append("a=rtpmap:98 H264/90000\r\n"); + //content.append("a=rtpmap:99 H265/90000\r\n"); + if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式 + content.append("a=setup:passive\r\n"); + content.append("a=connection:new\r\n"); + } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 + content.append("a=setup:active\r\n"); + content.append("a=connection:new\r\n"); + } + } + if (info.getType() == SessionType.download) { + content.append("a=downloadspeed:").append(info.getDownloadSpeed()).append("\r\n"); + } + content.append("y=").append(info.getSsrc()).append("\r\n");// ssrc + return content.toString(); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/Alarm.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/Alarm.java new file mode 100644 index 00000000..1556f8d5 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/Alarm.java @@ -0,0 +1,76 @@ +package com.fastbee.sip.server.msg; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.enums.AlarmMethod; +import com.fastbee.sip.enums.AlarmType; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Optional; + +@Getter +@Setter +@ToString +public class Alarm implements SipMessage { + @JacksonXmlProperty(localName = "DeviceID") + private String deviceId; + + @JacksonXmlProperty(localName = "AlarmPriority") + private String alarmPriority; + + @JacksonXmlProperty(localName = "AlarmTime") + private String alarmTime; + + @JacksonXmlProperty(localName = "AlarmMethod") + private AlarmMethod alarmMethod; + + @JacksonXmlProperty(localName = "Longitude") + private Float longitude; + + @JacksonXmlProperty(localName = "Latitude") + private Float latitude; + + @JacksonXmlProperty(localName = "AlarmDescription") + private String description; + + @JacksonXmlProperty(localName = "Info") + private Info info; + + @JacksonXmlProperty(localName = "SN") + private String sn; + + @Getter + @Setter + public static class Info { + + @JacksonXmlProperty(localName = "AlarmType") + private String alarmType; + + @JacksonXmlProperty(localName = "AlarmTypeParam") + private AlarmTypeParam alarmTypeParam; + + public Optional getAlarmTypeEnum(AlarmMethod method) { + return AlarmType.of(method, alarmType); + } + } + + @Getter + @Setter + public static class AlarmTypeParam { + //1-进入区域;2-离开区域 + @JacksonXmlProperty(localName = "EventType") + private String eventType; + } + + @Override + public String getDeviceId() { + return this.deviceId; + } + + @Override + public String getSn() { + return this.sn; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/CatalogInfo.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/CatalogInfo.java new file mode 100644 index 00000000..0e879985 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/CatalogInfo.java @@ -0,0 +1,104 @@ +package com.fastbee.sip.server.msg; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.model.GB28181DeviceChannel; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +import static com.fastbee.sip.util.SipUtil.safeString; + +@Getter +@Setter +public class CatalogInfo implements SipMessage { + @JacksonXmlProperty(localName = "DeviceID") + private String deviceId; + + @JacksonXmlProperty(localName = "SN") + private String sn; + + @JacksonXmlProperty(localName = "SumNum") + private String _sumNum; + + @JacksonXmlProperty(localName = "DeviceList") + private List channelList; + + @Override + public String getDeviceId() { + return null; + } + + @Override + public String getSn() { + return null; + } + + @Override + public int totalPart() { + return getSumNum(); + } + + @Override + public int numberOfPart() { + return channelList == null ? 0 : channelList.size(); + } + + public int getSumNum() { + return _sumNum == null ? 0 : Integer.parseInt(_sumNum); + } + + public void setSumNum(int sumNum) { + this._sumNum = String.valueOf(sumNum); + } + + public String toXml(String charset) { + StringBuilder body = new StringBuilder("\n" + + "\n" + + "Catalog\n" + + "" + getSn() + "\n" + + "" + getDeviceId() + "\n" + + "OK\n" + + "" + getSumNum() + "\n" + + ""); + + for (GB28181DeviceChannel channel : getChannelList()) { + + body + .append("\n") + .append("").append(channel.getChannelId()).append("\n") + .append("").append(safeString(channel.getName())).append("\n") + .append("").append(safeString(channel.getManufacturer())).append("\n") + .append("").append(safeString(channel.getModel())).append("\n") + .append("").append(safeString(channel.getOwner())).append("\n") + .append("").append(safeString(channel.getCivilCode())).append("\n") + .append("").append(safeString(channel.getBlock())).append("\n") + .append("").append(safeString(channel.getAddress())).append("\n") + .append("").append(safeString(channel.getParental())).append("\n") + .append("").append(safeString(channel.getParentId())).append("\n") + .append("").append(safeString(channel.getSafetyWay())).append("\n") + .append("").append(safeString(channel.getRegisterWay())).append("\n") + .append("").append(safeString(channel.getCertNum())).append("\n") + .append("").append(safeString(channel.getCertifiable())).append("\n") + .append("").append(safeString(channel.getErrCode())).append("\n") + .append("").append(safeString(channel.getEndTime())).append("\n") + .append("").append(safeString(channel.getSecrecy())).append("\n") + .append("").append(safeString(channel.getIpAddress())).append("\n") + .append("").append(safeString(channel.getPort())).append("\n") + .append("").append(safeString(channel.getPassword())).append("\n") + .append("").append(safeString(channel.getStatus().getCode())).append("\n") + .append("").append(safeString(channel.getLongitude())).append("\n") + .append("").append(safeString(channel.getLatitude())).append("\n"); + if (channel.getInfo() != null) { + body.append("") + .append(channel.getInfo().toXML()) + .append("\n"); + } + + body.append("\n"); + } + body.append("\n"); + return body.toString(); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/ConfigDownload.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/ConfigDownload.java new file mode 100644 index 00000000..8f093d7a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/ConfigDownload.java @@ -0,0 +1,130 @@ +package com.fastbee.sip.server.msg; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; + +import java.util.StringJoiner; + +@Getter +@Setter +public class ConfigDownload implements SipMessage { + @JacksonXmlProperty(localName = "DeviceID") + private String deviceId; + + @JacksonXmlProperty(localName = "SN") + private String sn; + + @JacksonXmlProperty(localName = "ConfigType") + private String configType; + + @JacksonXmlProperty(localName = "BasicParam") + private BasicParam basicParam; + + @JacksonXmlProperty(localName = "VideoParamOp") + private VideoParamOp videoParamOp; + + public enum ConfigType { + BasicParam, + VideoParamOpt, + SVACEncodeConfig, + SVACDecodeConfig + } + + @Getter + @Setter + public static class BasicParam { + + @JacksonXmlProperty(localName = "Name") + private String name; + + //注册过期时间 + @JacksonXmlProperty(localName = "Expiration") + private String expiration; + + //心跳间隔时间 + @JacksonXmlProperty(localName = "HeartBeatInterval") + private int heartBeatInterval; + + //心跳超时次数 + @JacksonXmlProperty(localName = "HeartBeatCount") + private int heartBeatCount = 5; + + //定位功能支持情况,取值:0-不支持;1-支持 GPS定位;2-支持北斗定位(可选, 默认取值为0) + @JacksonXmlProperty(localName = "PositionCapability") + private int positionCapability; + + //经度 + @JacksonXmlProperty(localName = "Longitude") + private float longitude; + + //纬度 + @JacksonXmlProperty(localName = "Latitude") + private float latitude; + + public String toXml() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add("" + name + ""); + joiner.add("" + expiration + ""); + joiner.add("" + heartBeatInterval + ""); + joiner.add("" + heartBeatCount + ""); + joiner.add("" + positionCapability + ""); + joiner.add("" + longitude + ""); + joiner.add("" + latitude + ""); + return joiner.toString(); + } + } + + @Getter + @Setter + public static class VideoParamOp { + + @JacksonXmlProperty(localName = "DownloadSpeed") + private String downloadSpeed; + + @JacksonXmlProperty(localName = "Resolution") + private String resolution; + + } + + public String toXml(int sn, String charset) { + StringJoiner joiner = new StringJoiner("\r\n"); + joiner.add(""); + joiner.add(""); + joiner.add("ConfigDownload"); + joiner.add("" + sn + ""); + joiner.add("" + deviceId + ""); + + if (configTypeIs(ConfigType.BasicParam) && getBasicParam() != null) { + joiner.add("" + getBasicParam().toXml() + ""); + } + + joiner.add(""); + + joiner.add(""); + + return joiner.toString(); + } + + @Override + public String toString() { + return JSON.toJSONString(this, SerializerFeature.PrettyFormat); + } + + public boolean configTypeIs(ConfigType type) { + return type.name().equals(configType); + } + + @Override + public String getDeviceId() { + return null; + } + + @Override + public String getSn() { + return null; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/DeviceControl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/DeviceControl.java new file mode 100644 index 00000000..f41d771f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/DeviceControl.java @@ -0,0 +1,163 @@ +package com.fastbee.sip.server.msg; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.enums.Direct; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.StringJoiner; + +@Getter +@Setter +@ToString +public class DeviceControl implements SipMessage { + @JacksonXmlProperty(localName = "DeviceID") + private String deviceId; + + @JacksonXmlProperty(localName = "SN") + private String sn; + + @JacksonXmlProperty(localName = "PTZCmd") + private String ptzCmd; + + @JacksonXmlProperty(localName = "RecordCmd") + private String recordCmd; + + @JacksonXmlProperty(localName = "GuardCmd") + private String guardCmd; + + @JacksonXmlProperty(localName = "AlarmCmd") + private String alarmCmd; + + @JacksonXmlProperty(localName = "IFameCmd") + private String iFameCmd; + + @JacksonXmlProperty(localName = "DragZoomIn") + private DragZoom dragZoomIn; + + @JacksonXmlProperty(localName = "DragZoomOut") + private DragZoom dragZoomOut; + + @Getter + @Setter + public static class DragZoom { + //播放窗口长度像素值 + @JacksonXmlProperty(localName = "Length") + private int length; + + //播放窗口宽度像素值 + @JacksonXmlProperty(localName = "Width") + private int width; + + //拉框中心的横轴坐标像素值 + @JacksonXmlProperty(localName = "MidPointX") + private int midPointX; + + //拉框中心的纵轴坐标像素值 + @JacksonXmlProperty(localName = "MidPointY") + private int midPointY; + + //拉框长度像素值 + @JacksonXmlProperty(localName = "LengthX") + private int lengthX; + + //拉框宽度像素值 + @JacksonXmlProperty(localName = "LengthY") + private int lengthY; + + } + + //看守位控制命令 + @Getter + @Setter + public static class HomePosition { + + //看守位使能1:开启,0:关闭 + @JacksonXmlProperty(localName = "Enabled") + private int enabled; + + //自动归位时间间隔,开启看守位时使用,单位:秒(s) + @JacksonXmlProperty(localName = "ResetTime") + private Integer resetTime; + + //调用预置位编号,开启看守位时使用,取值范围0~255 + @JacksonXmlProperty(localName = "PresetIndex") + private Integer presetIndex; + } + + @Getter + @Setter + public static class AlarmCmdInfo { + + //复位报警的报警方式属性 + @JacksonXmlProperty(localName = "AlarmMethod") + private String alarmMethod; + + @JacksonXmlProperty(localName = "AlarmType") + private String alarmType; + } + + public DeviceControl setPtzDirect(Map directAndSpeed) { + int code = 0; + StringBuilder cmd = new StringBuilder("A50F4D"); + for (Map.Entry entry : directAndSpeed.entrySet()) { + code = entry.getKey().merge(code); + } + //控制码 + cmd.append(String.format("%02X", code), 0, 2); + //水平控制速度 + int lrSpeed = directAndSpeed.getOrDefault(Direct.LEFT, directAndSpeed.getOrDefault(Direct.RIGHT, 0)); + cmd.append(String.format("%02X", lrSpeed), 0, 2); + //垂直控制速度 + int udSpeed = directAndSpeed.getOrDefault(Direct.UP, directAndSpeed.getOrDefault(Direct.DOWN, 0)); + cmd.append(String.format("%02X", udSpeed), 0, 2); + //缩放控制速度 + int zoomSpeed = directAndSpeed.getOrDefault(Direct.ZOOM_IN, directAndSpeed.getOrDefault(Direct.ZOOM_OUT, 0)) & 0xF; + cmd.append(String.format("%X", zoomSpeed), 0, 1) + .append("0"); + + //校验码 + int checkCode = (0XA5 + 0X0F + 0X4D + code + lrSpeed + udSpeed + (zoomSpeed << 4)) % 256; + cmd.append(String.format("%02X", checkCode), 0, 2); + setPtzCmd(cmd.toString()); + return this; + } + + public String toXml(int sn, String charset) { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add(""); + joiner.add(""); + joiner.add("DeviceControl"); + joiner.add("" + sn + ""); + joiner.add("" + deviceId + ""); + + if (isPtzControl()) { + joiner.add("" + getPtzCmd() + ""); + } + + joiner.add(""); + joiner.add("10"); + joiner.add(""); + joiner.add(""); + + return joiner.toString(); + } + + @Override + public String getDeviceId() { + return null; + } + + @Override + public String getSn() { + return null; + } + + public boolean isPtzControl() { + return StringUtils.hasText(ptzCmd); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/GB28181Device.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/GB28181Device.java new file mode 100644 index 00000000..54a9d305 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/GB28181Device.java @@ -0,0 +1,104 @@ +package com.fastbee.sip.server.msg; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +public class GB28181Device implements SipMessage { + @JsonProperty("DeviceID") + private String id; + + /** + * 设备名 + */ + @JsonProperty("DeviceName") + private String name; + + /** + * 生产厂商 + */ + @JsonProperty("Manufacturer") + private String manufacturer; + + /** + * 型号 + */ + @JsonProperty("Model") + private String model; + + /** + * 固件版本 + */ + @JsonProperty("Firmware") + private String firmware; + + /** + * 传输协议 + * UDP/TCP + */ + private String transport; + + /** + * 数据流传输模式 + */ + private StreamMode streamMode = StreamMode.UDP; + + /** + * 访问地址 + */ + private String host; + + /** + * 访问端口 + */ + private int port; + + /** + * 是否在线 + */ + private boolean online; + + @JsonProperty("Channel") + private int channelNumber; + + /** + * 通道列表 + */ + private List channelList; + + @JsonProperty("SN") + private String sn; + + /** + * 心跳间隔 + */ + private long heartBeatInterval = 300; + + @Override + public String getDeviceId() { + return null; + } + + @Override + public String getSn() { + return null; + } + + public enum StreamMode { + UDP, + TCP_ACTIVE,//主动模式 + TCP_PASSIVE//被动模式 + } + + public String getHostAndPort() { + return getHost() + ":" + getPort(); + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/KeepaliveMessage.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/KeepaliveMessage.java new file mode 100644 index 00000000..c413243c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/server/msg/KeepaliveMessage.java @@ -0,0 +1,29 @@ +package com.fastbee.sip.server.msg; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fastbee.sip.server.SipMessage; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class KeepaliveMessage implements SipMessage { + @JacksonXmlProperty(localName = "DeviceID") + private String deviceId; + + @JacksonXmlProperty(localName = "Status") + private String status; + + @JacksonXmlProperty(localName = "SN") + private String sn; + + @Override + public String getDeviceId() { + return null; + } + + @Override + public String getSn() { + return null; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IGatewayService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IGatewayService.java new file mode 100644 index 00000000..aa7e26d4 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IGatewayService.java @@ -0,0 +1,11 @@ +package com.fastbee.sip.service; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; + +import java.util.List; + +public interface IGatewayService { + void sendFunction(String deviceID,List functinos); + void sendFunction(String deviceID,String identifier,String value); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IInviteService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IInviteService.java new file mode 100644 index 00000000..7b5e0ca9 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IInviteService.java @@ -0,0 +1,26 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.enums.SessionType; +import com.fastbee.sip.model.InviteInfo; +import com.fastbee.sip.model.VideoSessionInfo; + +import java.util.List; + +public interface IInviteService { + + void updateInviteInfo(VideoSessionInfo sinfo, InviteInfo inviteInfo); + + InviteInfo getInviteInfo(SessionType type, + String deviceId, + String channelId, + String stream); + + List getInviteInfoAll(SessionType type, String deviceId, String channelId, String stream); + + InviteInfo getInviteInfoBySSRC(String ssrc); + + void removeInviteInfo(SessionType type, + String deviceId, + String channelId, + String stream); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMediaServerService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMediaServerService.java new file mode 100644 index 00000000..69b06e9a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMediaServerService.java @@ -0,0 +1,80 @@ +package com.fastbee.sip.service; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.model.VideoSessionInfo; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * 流媒体服务器配置Service接口 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +public interface IMediaServerService +{ + /** + * 查询流媒体服务器配置 + * + * @param id 流媒体服务器配置主键 + * @return 流媒体服务器配置 + */ + public MediaServer selectMediaServerById(Long id); + /** + * 查询流媒体服务器配置 + * + * @return 流媒体服务器配置 + */ + List selectMediaServer(); + MediaServer selectMediaServerBytenantId(Long tenantId); + MediaServer selectMediaServerBydeviceSipId(String deviceSipId); + + /** + * 查询流媒体服务器配置列表 + * + * @param mediaServer 流媒体服务器配置 + * @return 流媒体服务器配置集合 + */ + List selectMediaServerList(MediaServer mediaServer); + + /** + * 新增流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + int insertMediaServer(MediaServer mediaServer); + + /** + * 修改流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + int updateMediaServer(MediaServer mediaServer); + boolean syncMediaServer(MediaServer mediaServer,String secret); + /** + * 批量删除流媒体服务器配置 + * + * @param ids 需要删除的流媒体服务器配置主键集合 + * @return 结果 + */ + int deleteMediaServerByIds(Long[] ids); + + /** + * 删除流媒体服务器配置信息 + * + * @param id 流媒体服务器配置主键 + * @return 结果 + */ + int deleteMediaServerById(Long id); + + JSONObject getMediaList(String schema, String stream); + JSONObject listRtpServer(); + VideoSessionInfo createRTPServer(SipConfig sipConfig, MediaServer mediaInfo, SipDevice device, VideoSessionInfo videoSessionInfo); + MediaServer checkMediaServer(String ip, Long port, String secret); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMqttService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMqttService.java new file mode 100644 index 00000000..dc6bc738 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IMqttService.java @@ -0,0 +1,18 @@ +package com.fastbee.sip.service; + +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.server.msg.Alarm; + +import java.util.List; + +public interface IMqttService { + void publishInfo(SipDevice device); + void publishStatus(SipDevice device, int deviceStatus); + void publishEvent(Alarm alarm); + void publishProperty(Long productId, String deviceNum, List thingsList, int delay); + void publishChannelsProperty(String DeviceSipId, List channels); + void publishRecordsProperty(String DeviceSipId, RecordList recordList); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPlayService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPlayService.java new file mode 100644 index 00000000..9d56e6e1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPlayService.java @@ -0,0 +1,20 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.model.Stream; + +public interface IPlayService { + + Stream play(String deviceId, String channelId, boolean record); + + Stream playback(String deviceId, String channelId, String startTime, String endTime); + + String closeStream(String deviceId, String channelId, String streamId); + + String playbackPause(String deviceId, String channelId, String streamId); + + String playbackReplay(String deviceId, String channelId, String streamId); + + String playbackSeek(String deviceId, String channelId, String streamId, long seektime); + + String playbackSpeed(String deviceId, String channelId, String streamId, Integer speed); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPtzCmdService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPtzCmdService.java new file mode 100644 index 00000000..6adae46e --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IPtzCmdService.java @@ -0,0 +1,7 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.enums.Direct; + +public interface IPtzCmdService { + public boolean directPtzCmd(String deviceId, String channelId, Direct direct, Integer speed); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IRecordService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IRecordService.java new file mode 100644 index 00000000..3c5182e4 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IRecordService.java @@ -0,0 +1,30 @@ +package com.fastbee.sip.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.model.RecordItem; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.model.Stream; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +public interface IRecordService { + RecordList listDevRecord(String deviceId, String channelId, String startTime, String endTime); + List listRecord(String channelId, String sn); + + JSONObject listServerRecord(String recordApi, Integer pageNum, Integer pageSize); + JSONArray listServerRecordByDate(String recordApi, Integer year, Integer month, String app, String stream); + JSONObject listServerRecordByStream(String recordApi, Integer pageNum, Integer pageSize, String app); + JSONObject listServerRecordByApp(String recordApi, Integer pageNum, Integer pageSize); + JSONObject listServerRecordByFile(String recordApi, Integer pageNum, Integer pageSize, String app, String stream, String startTime, String endTime); + JSONObject listServerRecordByDevice(Integer pageNum, Integer pageSize, String deviceId, String channelId, String startTime, String endTime); + boolean startRecord(String stream); + boolean stopRecord(String stream); + boolean isRecording(String stream); + JSONObject getMp4RecordFile(String stream,String period); + Stream download(String deviceId, String channelId, + String startTime, String endTime, int downloadSpeed); + + Stream playRecord(String deviceId, String channelId); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipCacheService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipCacheService.java new file mode 100644 index 00000000..789de58a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipCacheService.java @@ -0,0 +1,31 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.ZlmMediaServer; + +public interface ISipCacheService { + Long getCSEQ(String serverSipId); + + void startPlay(Stream stream); + + Stream queryStreamByStreamId(String streamId); + + Stream queryPlayByDevice(String deviceId, String channelId, boolean record); + + void startPlayback(Stream stream); + + Stream queryPlaybackByStreamId(String streamId); + Stream queryPlaybackByDevice(String deviceId, String channelId); + + void startDownload(Stream stream); + + Stream queryDownloadByStreamId(String streamId); + Stream queryDownloadByDevice(String deviceId, String channelId); + + boolean stopStream(String streamId); + + void updateMediaInfo(ZlmMediaServer mediaServerConfig); + + void setRecordList(String key, RecordList recordList); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipConfigService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipConfigService.java new file mode 100644 index 00000000..f0e71656 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipConfigService.java @@ -0,0 +1,50 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.conf.SysSipConfig; +import com.fastbee.sip.domain.SipConfig; + +/** + * sip系统配置Service接口 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +public interface ISipConfigService +{ + void updateDefaultSipConfig(SipConfig sipConfig); + SipConfig GetDefaultSipConfig(); + /** + * 查询产品下第一条sip系统配置 + * + * @param id sip系统配置主键 + * @return sip系统配置 + */ + SipConfig selectSipConfigByProductId(Long productId); + SipConfig selectSipConfigBydeviceSipId(String deviceSipId); + + /** + * 新增sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + int insertSipConfig(SipConfig sipConfig); + + /** + * 修改sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + int updateSipConfig(SipConfig sipConfig); + void syncSipConfig(SysSipConfig sipConfig); + + /** + * 批量删除sip系统配置 + * + * @param ids 需要删除的sip系统配置主键集合 + * @return 结果 + */ + int deleteSipConfigByIds(Long[] ids); + int deleteSipConfigByProductIds(Long[] productIds); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceChannelService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceChannelService.java new file mode 100644 index 00000000..ced182e2 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceChannelService.java @@ -0,0 +1,71 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.model.BaseTree; + +import java.util.List; + +/** + * 监控设备通道信息Service接口 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +public interface ISipDeviceChannelService +{ + public void updateChannel(String deviceId, SipDeviceChannel channel); + /** + * 查询监控设备通道信息 + * + * @param channelId 监控设备通道信息主键 + * @return 监控设备通道信息 + */ + public SipDeviceChannel selectSipDeviceChannelByChannelId(Long channelId); + + public SipDeviceChannel selectSipDeviceChannelByChannelSipId(String channelSipId); + + public List selectSipDeviceChannelByDeviceSipId(String deviceSipId); + + /** + * 查询监控设备通道信息列表 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 监控设备通道信息集合 + */ + public List selectSipDeviceChannelList(SipDeviceChannel sipDeviceChannel); + + /** + * 新增监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + public int insertSipDeviceChannel(SipDeviceChannel sipDeviceChannel); + public String insertSipDeviceChannelGen(Long createNum, SipDeviceChannel sipDeviceChannel); + /** + * 修改监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + public int updateSipDeviceChannel(SipDeviceChannel sipDeviceChannel); + public int updateSipDeviceChannelStatus(String ChannelId, Integer status); + + /** + * 批量删除监控设备通道信息 + * + * @param channelIds 需要删除的监控设备通道信息主键集合 + * @return 结果 + */ + public int deleteSipDeviceChannelByChannelIds(Long[] channelIds); + + /** + * 删除监控设备通道信息信息 + * + * @param channelId 监控设备通道信息主键 + * @return 结果 + */ + public int deleteSipDeviceChannelByChannelId(Long channelId); + + public List> queryVideoDeviceTree(String deviceId, String parentId, boolean onlyCatalog); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceService.java new file mode 100644 index 00000000..c5d9f6ea --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/ISipDeviceService.java @@ -0,0 +1,69 @@ +package com.fastbee.sip.service; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.model.BaseTree; + +import java.util.List; + +/** + * 监控设备Service接口 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +public interface ISipDeviceService +{ + boolean exists(String sipId); + boolean updateDevice(SipDevice device); + /** + * 查询监控设备 + * + * @param deviceId 监控设备主键 + * @return 监控设备 + */ + public SipDevice selectSipDeviceByDeviceId(Long deviceId); + public SipDevice selectSipDeviceBySipId(String sipId); + /** + * 查询监控设备列表 + * + * @param sipDevice 监控设备 + * @return 监控设备集合 + */ + public List selectSipDeviceList(SipDevice sipDevice); + public List> selectSipDeviceChannelList(String deviceId); + /** + * 新增监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + public int insertSipDevice(SipDevice sipDevice); + + /** + * 修改监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + public int updateSipDevice(SipDevice sipDevice); + + public int updateSipDeviceStatus(SipDevice sipDevice); + + /** + * 批量删除监控设备 + * + * @param deviceIds 需要删除的监控设备主键集合 + * @return 结果 + */ + public int deleteSipDeviceByDeviceIds(Long[] deviceIds); + + /** + * 删除监控设备信息 + * + * @param deviceId 监控设备主键 + * @return 结果 + */ + public int deleteSipDeviceByDeviceId(String deviceId); + public int deleteSipDeviceBySipId(String SipId); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IZmlHookService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IZmlHookService.java new file mode 100644 index 00000000..e3d03534 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/IZmlHookService.java @@ -0,0 +1,22 @@ +package com.fastbee.sip.service; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.VideoSessionInfo; + +public interface IZmlHookService { + JSONObject onHttpAccess(JSONObject json); + JSONObject onPlay(JSONObject json); + JSONObject onPublish(JSONObject json); + JSONObject onStreamNoneReader(JSONObject json); + JSONObject onStreamNotFound(JSONObject json); + JSONObject onStreamChanged(JSONObject json); + JSONObject onFlowReport(JSONObject json); + JSONObject onRtpServerTimeout(JSONObject json); + JSONObject onSendRtpStopped(JSONObject json); + JSONObject onRecordMp4(JSONObject json); + JSONObject onServerStarted(JSONObject json); + JSONObject onServerKeepalive(JSONObject json); + JSONObject onServerExited(JSONObject json); + Stream updateStream(VideoSessionInfo sinfo); +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/GatewayServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/GatewayServiceImpl.java new file mode 100644 index 00000000..83b12fc5 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/GatewayServiceImpl.java @@ -0,0 +1,41 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.sip.enums.FunctionType; +import com.fastbee.sip.service.IGatewayService; +import com.fastbee.sip.service.IPlayService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; + + +@Slf4j +@Service +public class GatewayServiceImpl implements IGatewayService { + @Autowired + private IPlayService playService; + + @Override + public void sendFunction(String deviceID, List functinos) { + + } + + @Override + public void sendFunction(String deviceID, String identifier, String value) { + FunctionType Type = FunctionType.fromType(identifier); + switch (Objects.requireNonNull(Type)) { + case VIDEOPUSH: + playService.play(deviceID, value,false); + break; + case AUDIOBROADCAST: + break; + case OTHER: + break; + } + } + + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/InviteServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/InviteServiceImpl.java new file mode 100644 index 00000000..2af284b7 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/InviteServiceImpl.java @@ -0,0 +1,127 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.sip.enums.SessionType; +import com.fastbee.sip.model.InviteInfo; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.service.IInviteService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Service +public class InviteServiceImpl implements IInviteService { + @Autowired + private RedisCache redisCache; + + @Override + public void updateInviteInfo(VideoSessionInfo sinfo, InviteInfo inviteInfo) { + InviteInfo invite = getInviteInfo(sinfo.getType(), sinfo.getDeviceId(), + sinfo.getChannelId(), sinfo.getStream()); + if (invite == null) { + log.info("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}", + sinfo.getDeviceId(), sinfo.getChannelId(), sinfo.getStream()); + invite = inviteInfo; + } + if (inviteInfo.getSsrc() != null) { + invite.setSsrc(inviteInfo.getSsrc()); + } + if (inviteInfo.getCallId() != null) { + invite.setCallId(inviteInfo.getCallId()); + } + if (inviteInfo.getPort() != 0) { + invite.setPort(inviteInfo.getPort()); + } + + String key = RedisKeyBuilder.buildInviteCacheKey( + sinfo.getType() != null ? sinfo.getType().name() : "*", + sinfo.getDeviceId() != null ? sinfo.getDeviceId() : "*", + sinfo.getChannelId() != null ? sinfo.getChannelId() : "*", + sinfo.getStream() != null ? sinfo.getStream() : "*", + inviteInfo.getSsrc() != null ? inviteInfo.getSsrc() : "*"); + redisCache.setCacheObject(key, invite); + } + + @Override + public InviteInfo getInviteInfo(SessionType type, String deviceId, String channelId, String stream) { + String key = RedisKeyBuilder.buildInviteCacheKey( + type != null ? type.name() : "*", + deviceId != null ? deviceId : "*", + channelId != null ? channelId : "*", + stream != null ? stream : "*", + "*"); + List scanResult = redisCache.scan(key); + if (scanResult.isEmpty()) { + return null; + } + if (scanResult.size() != 1) { + log.warn("[获取InviteInfo] 发现 key: {}存在多条", key); + } + + return (InviteInfo) redisCache.getCacheObject((String) scanResult.get(0)); + } + + @Override + public List getInviteInfoAll(SessionType type, String deviceId, String channelId, String stream) { + String key = RedisKeyBuilder.buildInviteCacheKey( + type != null ? type.name() : "*", + deviceId != null ? deviceId : "*", + channelId != null ? channelId : "*", + stream != null ? stream : "*", + "*"); + List scanResult = redisCache.scan(key); + if (scanResult.size() != 1) { + log.warn("[获取InviteInfo] 发现 key: {}存在多条", key); + } + if (scanResult.size() > 0) { + List list = new ArrayList<>(); + for (Object keyObj : scanResult) { + list.add((InviteInfo) redisCache.getCacheObject((String) keyObj)); + } + return list; + } else { + return Collections.emptyList(); + } + } + + @Override + public InviteInfo getInviteInfoBySSRC(String ssrc) { + String key = RedisKeyBuilder.buildInviteCacheKey("*", + "*", + "*", + "*", + ssrc); + List scanResult = redisCache.scan(key); + if (scanResult.size() != 1) { + return null; + } + return (InviteInfo) redisCache.getCacheObject((String) scanResult.get(0)); + } + + @Override + public void removeInviteInfo(SessionType type, String deviceId, String channelId, String stream) { + String scanKey = RedisKeyBuilder.buildInviteCacheKey( + type != null ? type.name() : "*", + deviceId != null ? deviceId : "*", + channelId != null ? channelId : "*", + stream != null ? stream : "*", + "*"); + List scanResult = redisCache.scan(scanKey); + if (scanResult.size() > 0) { + for (Object keyObj : scanResult) { + String key = (String) keyObj; + InviteInfo inviteInfo = (InviteInfo) redisCache.getCacheObject(key); + if (inviteInfo == null) { + continue; + } + redisCache.deleteObject(key); + } + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/MediaServerServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/MediaServerServiceImpl.java new file mode 100644 index 00000000..b3af23e6 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/MediaServerServiceImpl.java @@ -0,0 +1,328 @@ +package com.fastbee.sip.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.mapper.DeviceMapper; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.mapper.MediaServerMapper; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.model.ZlmMediaServer; +import com.fastbee.sip.server.VideoSessionManager; +import com.fastbee.sip.service.IMediaServerService; +import com.fastbee.sip.util.ZlmApiUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.util.*; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; +import static com.fastbee.common.utils.SecurityUtils.isAdmin; + +/** + * 流媒体服务器配置Service业务层处理 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Slf4j +@Service +public class MediaServerServiceImpl implements IMediaServerService { + @Autowired + private MediaServerMapper mediaServerMapper; + @Autowired + private DeviceMapper deviceMapper; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Autowired + private VideoSessionManager streamSession; + + @Override + public MediaServer selectMediaServerById(Long id) { + return mediaServerMapper.selectMediaServerById(id); + } + + /** + * 查询流媒体服务器配置 + * + * @return 流媒体服务器配置 + */ + @Override + public List selectMediaServer() { + MediaServer mediaServer = new MediaServer(); + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看第一条流媒体服务器配置 + mediaServer.setTenantId(user.getUserId()); + } + } + return mediaServerMapper.selectMediaServer(mediaServer); + } + + @Override + public MediaServer selectMediaServerBytenantId(Long tenantId) { + List list = mediaServerMapper.selectMediaServerBytenantId(tenantId); + if (list.size() == 0) { + return selectMediaServerBytenantId(1L); + } else if (list.size() == 1) { + return list.get(0); + } else { + //随机选择一个流媒体节点 + Random random = new Random(); + int index = random.nextInt(list.size()); + return list.get(index); + } + } + + @Override + public MediaServer selectMediaServerBydeviceSipId(String deviceSipId) { + Device device = deviceMapper.selectDeviceBySerialNumber(deviceSipId); + if (device != null) { + List list = mediaServerMapper.selectMediaServerBytenantId(device.getTenantId()); + if (list.size() == 0) { + return selectMediaServerBytenantId(1L); + } else if (list.size() == 1) { + return list.get(0); + } else { + //随机选择一个流媒体节点 + Random random = new Random(); + int index = random.nextInt(list.size()); + return list.get(index); + } + } else { + return selectMediaServerBytenantId(1L); + } + + } + + /** + * 查询流媒体服务器配置列表 + * + * @param mediaServer 流媒体服务器配置 + * @return 流媒体服务器配置 + */ + @Override + public List selectMediaServerList(MediaServer mediaServer) { + SysUser user = getLoginUser().getUser(); +// List roles=user.getRoles(); +// // 租户 +// if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ +// mediaServer.setTenantId(user.getUserId()); +// } + mediaServer.setTenantId(user.getUserId()); + return mediaServerMapper.selectMediaServerList(mediaServer); + } + + /** + * 新增流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + @Override + public int insertMediaServer(MediaServer mediaServer) { + // 判断是否为管理员 + mediaServer.setIsSys(1); + SysUser user = getLoginUser().getUser(); +// List roles=user.getRoles(); +// for(int i=0;i list = mediaServerMapper.selectMediaServerList(temp); + if (list.size() > 0) { + return 0; + } + mediaServer.setCreateTime(DateUtils.getNowDate()); + boolean ret = syncMediaServer(mediaServer, mediaServer.getSecret()); + if (ret) { + return mediaServerMapper.insertMediaServer(mediaServer); + } else { + return 0; + } + } + + /** + * 修改流媒体服务器配置 + * + * @param mediaServer 流媒体服务器配置 + * @return 结果 + */ + @Override + public int updateMediaServer(MediaServer mediaServer) { + if (mediaServer.getId() != null) { + String newSecret = mediaServer.getSecret(); + mediaServer.setSecret(mediaServerMapper.selectMediaServerById(mediaServer.getId()).getSecret()); + boolean ret = syncMediaServer(mediaServer, newSecret); + if (ret) { + mediaServer.setSecret(newSecret); + mediaServer.setUpdateTime(DateUtils.getNowDate()); + return mediaServerMapper.updateMediaServer(mediaServer); + } + } + return 0; + } + + @Override + public boolean syncMediaServer(MediaServer mediaServer, String secret) { + String hookPrex = String.format("http://%s/zlmhook", mediaServer.getHookurl()); + Map param = new HashMap<>(); + param.put("api.secret", secret); + + param.put("hook.enable", "1"); + param.put("general.mediaServerId", mediaServer.getServerId()); + param.put("general.flowThreshold", "64"); + param.put("hook.on_play", String.format("%s/on_play", hookPrex)); + param.put("hook.on_http_access", String.format("%s/on_http_access", hookPrex)); + param.put("hook.on_publish", String.format("%s/on_publish", hookPrex)); + param.put("hook.on_server_started", String.format("%s/on_server_started", hookPrex)); + param.put("hook.on_stream_changed", String.format("%s/on_stream_changed", hookPrex)); + param.put("hook.on_stream_none_reader", String.format("%s/on_stream_none_reader", hookPrex)); + param.put("hook.on_stream_not_found", String.format("%s/on_stream_not_found", hookPrex)); + param.put("hook.on_send_rtp_stopped", String.format("%s/on_send_rtp_stopped", hookPrex)); + param.put("hook.on_rtp_server_timeout", String.format("%s/on_rtp_server_timeout", hookPrex)); + param.put("hook.on_flow_report", String.format("%s/on_flow_report", hookPrex)); + param.put("hook.on_server_keepalive", String.format("%s/on_server_keepalive", hookPrex)); + param.put("hook.on_server_exited", String.format("%s/on_server_exited", hookPrex)); + if (Objects.equals(mediaServer.getProtocol(), "http")) { + param.put("hook.on_record_mp4", String.format("http://127.0.0.1:%s/zlmhook/on_record_mp4", mediaServer.getRecordPort())); + } else if (Objects.equals(mediaServer.getProtocol(), "https")) { + param.put("hook.on_record_mp4", String.format("https://%s:%s/zlmhook/on_record_mp4", mediaServer.getDomain(), mediaServer.getRecordPort())); + } + param.put("hook.on_record_ts", ""); + param.put("hook.on_rtsp_auth", ""); + param.put("hook.on_rtsp_realm", ""); + param.put("hook.on_shell_login", ""); + param.put("hook.timeoutSec", "20"); + param.put("hook.alive_interval", "60.0"); + + param.put("rtsp.port", mediaServer.getPortRtsp().toString()); + param.put("rtmp.port", mediaServer.getPortRtmp().toString()); + + param.put("record.appName", "record"); + param.put("record.filePath", "./record"); + if (!ObjectUtils.isEmpty(mediaServer.getRtpPortRange())) { + param.put("rtp_proxy.port_range", mediaServer.getRtpPortRange().replace(",", "-")); + log.warn("[ZLM] 修改RTP推流端口时,请同步Docker端口映射"); + } + JSONObject responseJSON = zlmApiUtils.setServerConfig(mediaServer, param); + if (responseJSON != null && responseJSON.getInteger("code") == 0) { + log.info("[ZLM] 设置成功,开始重启以保证配置生效"); + zlmApiUtils.restartServer(mediaServer); + return true; + } else { + log.info("[ZLM] 设置zlm失败 {}", responseJSON); + return false; + } + } + + /** + * 批量删除流媒体服务器配置 + * + * @param ids 需要删除的流媒体服务器配置主键 + * @return 结果 + */ + @Override + public int deleteMediaServerByIds(Long[] ids) { + return mediaServerMapper.deleteMediaServerByIds(ids); + } + + /** + * 删除流媒体服务器配置信息 + * + * @param id 流媒体服务器配置主键 + * @return 结果 + */ + @Override + public int deleteMediaServerById(Long id) { + return mediaServerMapper.deleteMediaServerById(id); + } + + @Override + public JSONObject getMediaList(String schema, String stream) { + SysUser user = getLoginUser().getUser(); + MediaServer media = selectMediaServerBytenantId(user.getUserId()); + if (media != null) { + return zlmApiUtils.getMediaList(media, "live", schema, stream).getJSONObject("data"); + } + return null; + } + + @Override + public JSONObject listRtpServer() { + SysUser user = getLoginUser().getUser(); + MediaServer media = selectMediaServerBytenantId(user.getUserId()); + if (media != null) { + return zlmApiUtils.listRtpServer(media).getJSONObject("data"); + } + return null; + } + + @Override + public VideoSessionInfo createRTPServer(SipConfig sipConfig, MediaServer mediaInfo, SipDevice device, VideoSessionInfo videoSessionInfo) { + switch (videoSessionInfo.getType()) { + case play: + videoSessionInfo.setSsrc(streamSession.createPlaySsrc(sipConfig.getDomain())); + videoSessionInfo.setStream(String.format("gb_play_%s_%s", device.getDeviceSipId(), videoSessionInfo.getChannelId())); + break; + case playrecord: + videoSessionInfo.setSsrc(streamSession.createPlaySsrc(sipConfig.getDomain())); + videoSessionInfo.setStream(String.format("gb_playrecord_%s_%s", device.getDeviceSipId(), videoSessionInfo.getChannelId())); + break; + case playback: + videoSessionInfo.setSsrc(streamSession.createPlayBackSsrc(sipConfig.getDomain())); + videoSessionInfo.setStream(videoSessionInfo.getSsrc()); + break; + case download: + videoSessionInfo.setSsrc(streamSession.createPlayBackSsrc(sipConfig.getDomain())); + videoSessionInfo.setStream(videoSessionInfo.getSsrc()); + break; + } + + int mediaPort = zlmApiUtils.createRTPServer(mediaInfo, videoSessionInfo.getStream(), videoSessionInfo.getSsrc(), false, false, 0); + videoSessionInfo.setPort(mediaPort); + return videoSessionInfo; + } + + @Override + public MediaServer checkMediaServer(String ip, Long port, String secret) { + MediaServer mediaServerItem = new MediaServer(); + mediaServerItem.setIp(ip); + mediaServerItem.setPortHttp(port); + mediaServerItem.setSecret(secret); + ZlmMediaServer zlmServerConfig = zlmApiUtils.getMediaServerConfig(mediaServerItem); + if (zlmServerConfig == null) { + return null; + } + mediaServerItem.setServerId(zlmServerConfig.getMediaServerId()); + mediaServerItem.setPortRtmp(Long.valueOf(zlmServerConfig.getRtmpPort())); + mediaServerItem.setPortRtsp(Long.valueOf(zlmServerConfig.getRtspPort())); + mediaServerItem.setRtpProxyPort(Long.valueOf(zlmServerConfig.getRtpProxyPort())); + mediaServerItem.setHookurl("java:8080"); + return mediaServerItem; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PlayServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PlayServiceImpl.java new file mode 100644 index 00000000..6c2827e1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PlayServiceImpl.java @@ -0,0 +1,154 @@ +package com.fastbee.sip.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.server.IRtspCmd; +import com.fastbee.sip.server.ISipCmd; +import com.fastbee.sip.server.VideoSessionManager; +import com.fastbee.sip.service.*; +import com.fastbee.sip.util.ZlmApiUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class PlayServiceImpl implements IPlayService { + + @Autowired + private ISipCmd sipCmd; + + @Autowired + private IRtspCmd rtspCmd; + + @Autowired + private IZmlHookService zmlHookService; + + @Autowired + private VideoSessionManager streamSession; + + @Autowired + private ISipDeviceService sipDeviceService; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Override + public Stream play(String deviceId, String channelId, boolean record) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + if (dev == null) { + log.error("play dev is null,deviceId:{},channelId:{}", deviceId, channelId); + return null; + } + String streamid; + if (record) { + streamid = String.format("gb_playrecord_%s_%s", deviceId, channelId); + } else { + streamid = String.format("gb_play_%s_%s", deviceId, channelId); + } + VideoSessionInfo sinfo = streamSession.getSessionInfo(deviceId, channelId, streamid, null); + if (sinfo == null) { + VideoSessionInfo info = sipCmd.playStreamCmd(dev, channelId, record); + return zmlHookService.updateStream(info); + } else { + log.info("sinfo: {}", sinfo); + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(deviceId); + JSONObject rtpInfo = zlmApiUtils.getRtpInfo(mediaInfo, streamid); + if (rtpInfo.getInteger("code") == 0) { + if (rtpInfo.getBoolean("exist") && rtpInfo.getInteger("local_port") != 0) { + //直播 + if (sinfo.isPushing() && !record) { + return zmlHookService.updateStream(sinfo); + } + //直播录像 + if (sinfo.isRecording() && record) { + return zmlHookService.updateStream(sinfo); + } + } + } + //清理会话后 重新发起播放 + sipCmd.streamByeCmd(dev, channelId, streamid, null); + VideoSessionInfo info = sipCmd.playStreamCmd(dev, channelId, record); + return zmlHookService.updateStream(info); + } + } + + @Override + public String closeStream(String deviceId, String channelId, String streamId) { + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(deviceId); + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + JSONObject ret = zlmApiUtils.getMediaList(mediaInfo, "rtp", "rtmp", streamId); + int code = ret.getInteger("code"); + if (code == 0) { + int readerCount = ret.getInteger("readerCount"); + log.info("还有{}位用户正在观看该流!", readerCount); + if (readerCount < 2) { + sipCmd.streamByeCmd(dev, channelId, streamId, null); + } + } else { + log.info("流详细信息:{},错误码:{}", ret, code); + } + return ""; + } + + @Override + public Stream playback(String deviceId, String channelId, String startTime, String endTime) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo info = sipCmd.playbackStreamCmd(dev, channelId, startTime, endTime); + return zmlHookService.updateStream(info); + } + + @Override + public String playbackPause(String deviceId, String channelId, String streamId) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo sinfo = streamSession.getSessionInfo(deviceId, channelId, streamId, null); + if (null == sinfo) { + return "streamId不存在"; + } + rtspCmd.setCseq(sinfo.getStream()); + rtspCmd.playPause(dev, channelId, streamId); + return null; + } + + @Override + public String playbackReplay(String deviceId, String channelId, String streamId) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo sinfo = streamSession.getSessionInfo(deviceId, channelId, streamId, null); + if (null == sinfo) { + return "streamId不存在"; + } + rtspCmd.setCseq(streamId); + rtspCmd.playReplay(dev, channelId, streamId); + return null; + } + + @Override + public String playbackSeek(String deviceId, String channelId, String streamId, long seektime) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo sinfo = streamSession.getSessionInfo(deviceId, channelId, streamId, null); + if (null == sinfo) { + return "streamId不存在"; + } + rtspCmd.setCseq(streamId); + rtspCmd.playBackSeek(dev, channelId, streamId, seektime); + return null; + } + + @Override + public String playbackSpeed(String deviceId, String channelId, String streamId, Integer speed) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo sinfo = streamSession.getSessionInfo(deviceId, channelId, streamId, null); + if (null == sinfo) { + return "streamId不存在"; + } + rtspCmd.setCseq(streamId); + rtspCmd.playBackSpeed(dev, channelId, streamId, speed); + return null; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PtzCmdServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PtzCmdServiceImpl.java new file mode 100644 index 00000000..837fde43 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/PtzCmdServiceImpl.java @@ -0,0 +1,42 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.enums.Direct; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.server.msg.DeviceControl; +import com.fastbee.sip.service.IPtzCmdService; +import com.fastbee.sip.service.ISipDeviceService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + + +@Slf4j +@Service +public class PtzCmdServiceImpl implements IPtzCmdService { + + @Autowired + private MessageInvoker messageInvoker; + + @Autowired + private ISipDeviceService sipDeviceService; + + + public boolean directPtzCmd(String deviceId, String channelId, Direct direct, Integer speed) { + Map directAndSpeed = new HashMap<>(); + directAndSpeed.put(direct, speed); + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + if (dev != null) { + DeviceControl control = new DeviceControl(); + control.setPtzDirect(directAndSpeed); + control.setDeviceId(channelId); + messageInvoker.deviceControl(dev, control); + return true; + } + return false; + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/RecordServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/RecordServiceImpl.java new file mode 100644 index 00000000..ccb8e062 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/RecordServiceImpl.java @@ -0,0 +1,192 @@ +package com.fastbee.sip.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.model.RecordItem; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.server.ISipCmd; +import com.fastbee.sip.server.MessageInvoker; +import com.fastbee.sip.server.RecordCacheManager; +import com.fastbee.sip.service.*; +import com.fastbee.sip.util.RecordApiUtils; +import com.fastbee.sip.util.SipUtil; +import com.fastbee.sip.util.ZlmApiUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +@Slf4j +@Service +public class RecordServiceImpl implements IRecordService { + + @Autowired + private MessageInvoker messageInvoker; + + @Autowired + private RedisCache redisCache; + + @Autowired + private RecordCacheManager recordCacheManager; + + @Autowired + private ISipDeviceService sipDeviceService; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private IZmlHookService zmlHookService; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Autowired + private RecordApiUtils recordApiUtils; + + @Autowired + private ISipCmd sipCmd; + + @Autowired + private IPlayService playService; + + + @Override + public RecordList listDevRecord(String deviceId, String channelId, String start, String end) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + if (dev != null) { + String sn = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); + String recordkey = channelId + ":" + sn; + recordCacheManager.addlock(recordkey); + messageInvoker.recordInfoQuery(dev, sn, channelId, SipUtil.timestampToDate(start), SipUtil.timestampToDate(end)); + String catchkey = RedisKeyBuilder.buildSipRecordinfoCacheKey(recordkey); + return (RecordList) messageInvoker.getExecResult(catchkey, SipUtil.DEFAULT_EXEC_TIMEOUT); + } + return null; + } + + @Override + public List listRecord(String channelId, String sn) { + String recordkey = channelId + ":" + sn; + String catchkey = RedisKeyBuilder.buildSipRecordinfoCacheKey(recordkey); + List items = redisCache.getCacheList(catchkey); + if (items.size() > 1) { + items.sort(Comparator.naturalOrder()); + } + return items; + } + + @Override + public JSONObject listServerRecord(String recordApi, Integer pageNum, Integer pageSize) { + return recordApiUtils.getRecordlist(recordApi, pageNum, pageSize, null).getJSONObject("data"); + } + + @Override + public JSONArray listServerRecordByDate(String recordApi, Integer year, Integer month, String app, String stream) { + return recordApiUtils.getRecordDatelist(recordApi, year, month, app, stream, null).getJSONArray("data"); + } + + @Override + public JSONObject listServerRecordByStream(String recordApi, Integer pageNum, Integer pageSize, String app) { + return recordApiUtils.getRecordStreamlist(recordApi, pageNum, pageSize, app, null).getJSONObject("data"); + } + + @Override + public JSONObject listServerRecordByApp(String recordApi, Integer pageNum, Integer pageSize) { + return recordApiUtils.getRecordApplist(recordApi, pageNum, pageSize, null).getJSONObject("data"); + } + + @Override + public JSONObject listServerRecordByFile(String recordApi, Integer pageNum, Integer pageSize, String app, String stream, String startTime, String endTime) { + return recordApiUtils.getRecordFilelist(recordApi, pageNum, pageSize, app, stream, startTime, endTime, null).getJSONObject("data"); + } + + @Override + public JSONObject listServerRecordByDevice(Integer pageNum, Integer pageSize, String deviceId, String channelId, String startTime, String endTime) { + String stream = "gb_play_" + deviceId + "_" + channelId; + MediaServer mediaServer = mediaServerService.selectMediaServerBydeviceSipId(deviceId); + String recordApi = ""; + if (mediaServer != null && Objects.equals(mediaServer.getProtocol(), "http")) { + recordApi = "http://" + mediaServer.getIp() + ":" + mediaServer.getRecordPort(); + } else if (mediaServer != null && Objects.equals(mediaServer.getProtocol(), "https")) { + recordApi = "https://" + mediaServer.getDomain() + ":" + mediaServer.getRecordPort(); + } + JSONObject obj = recordApiUtils.getRecordFilelist(recordApi, pageNum, pageSize, "rtp", + stream, startTime, endTime, null); + if (obj != null) { + obj = obj.getJSONObject("data"); + obj.put("recordApi", recordApi); + log.info("obj:{}", obj); + } + return obj; + } + + @Override + public boolean startRecord(String stream) { + SysUser user = getLoginUser().getUser(); + //缓存zlm服务器配置 + MediaServer media = mediaServerService.selectMediaServerBytenantId(user.getUserId()); + if (media != null) { + return zlmApiUtils.startRecord(media, "1", "live", stream).getBoolean("result"); + } + return false; + } + + @Override + public boolean stopRecord(String stream) { + SysUser user = getLoginUser().getUser(); + MediaServer media = mediaServerService.selectMediaServerBytenantId(user.getUserId()); + ; + if (media != null) { + return zlmApiUtils.stopRecord(media, "1", "live", stream).getBoolean("result"); + } + return false; + } + + @Override + public boolean isRecording(String stream) { + SysUser user = getLoginUser().getUser(); + MediaServer media = mediaServerService.selectMediaServerBytenantId(user.getUserId()); + ; + if (media != null) { + return zlmApiUtils.isRecording(media, "1", "live", stream).getBoolean("status"); + } + return false; + } + + @Override + public JSONObject getMp4RecordFile(String stream, String period) { + SysUser user = getLoginUser().getUser(); + MediaServer media = mediaServerService.selectMediaServerBytenantId(user.getUserId()); + if (media != null) { + return zlmApiUtils.getMp4RecordFile(media, period, "live", stream).getJSONObject("data"); + } + return null; + } + + @Override + public Stream download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed) { + SipDevice dev = sipDeviceService.selectSipDeviceBySipId(deviceId); + VideoSessionInfo info = sipCmd.downloadStreamCmd(dev, channelId, startTime, endTime, downloadSpeed); + return zmlHookService.updateStream(info); + } + + + + @Override + public Stream playRecord(String deviceId, String channelId) { + return playService.play(deviceId, channelId, true); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipCacheServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipCacheServiceImpl.java new file mode 100644 index 00000000..1591e394 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipCacheServiceImpl.java @@ -0,0 +1,102 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.ZlmMediaServer; +import com.fastbee.sip.service.ISipCacheService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class SipCacheServiceImpl implements ISipCacheService { + + @Autowired + private RedisCache redisCache; + + @Override + public Long getCSEQ(String serverSipId) { + String key = RedisKeyBuilder.buildSipCSEQCacheKey(serverSipId); + long result = redisCache.incr(key, 1L); + if (result > Integer.MAX_VALUE) { + redisCache.setCacheObject(key, 1); + result = 1; + } + return result; + } + + @Override + public Stream queryStreamByStreamId(String streamId) { + String key = RedisKeyBuilder.buildStreamCacheKey(streamId); + return (Stream) redisCache.getCacheObject(key); + } + + @Override + public void startPlay(Stream stream) { + String key = RedisKeyBuilder.buildStreamCacheKey(stream.getStreamId()); + redisCache.setCacheObject(key, stream); + } + + @Override + public boolean stopStream(String streamId) { + String key = RedisKeyBuilder.buildStreamCacheKey(streamId); + return redisCache.deleteObject(key); + } + + @Override + public Stream queryPlayByDevice(String deviceId, String channelId, boolean record) { + String streamId; + streamId = String.format("gb_play_%s_%s", deviceId, channelId); + String key = RedisKeyBuilder.buildStreamCacheKey(streamId); + return (Stream) redisCache.getCacheObject(key); + } + + @Override + public void startPlayback(Stream stream) { + String key = RedisKeyBuilder.buildStreamCacheKey(stream.getStreamId()); + redisCache.setCacheObject(key, stream); + } + + @Override + public Stream queryPlaybackByStreamId(String streamId) { + String key = RedisKeyBuilder.buildStreamCacheKey(streamId); + return (Stream) redisCache.getCacheObject(key); + } + + @Override + public Stream queryPlaybackByDevice(String deviceId, String channelId) { + return null; + } + + @Override + public void startDownload(Stream stream) { + String key = RedisKeyBuilder.buildStreamCacheKey(stream.getStreamId()); + redisCache.setCacheObject(key, stream); + } + + @Override + public Stream queryDownloadByStreamId(String streamId) { + String key = RedisKeyBuilder.buildStreamCacheKey(streamId); + return (Stream) redisCache.getCacheObject(key); + } + + @Override + public Stream queryDownloadByDevice(String deviceId, String channelId) { + return null; + } + + @Override + public void updateMediaInfo(ZlmMediaServer mediaServerConfig) { + redisCache.setCacheObject(FastBeeConstant.REDIS.DEFAULT_MEDIA_CONFIG, mediaServerConfig); + } + + @Override + public void setRecordList(String key, RecordList recordList) { + String catchkey = RedisKeyBuilder.buildSipRecordinfoCacheKey(key); + redisCache.setCacheObject(catchkey, recordList); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipConfigServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipConfigServiceImpl.java new file mode 100644 index 00000000..7b111229 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipConfigServiceImpl.java @@ -0,0 +1,155 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.mapper.DeviceMapper; +import com.fastbee.sip.conf.SysSipConfig; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.mapper.SipConfigMapper; +import com.fastbee.sip.service.ISipConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * sip系统配置Service业务层处理 + * + * @author zhuangpeng.li + * @date 2022-11-30 + */ +@Service +public class SipConfigServiceImpl implements ISipConfigService { + @Autowired + private SipConfigMapper sipConfigMapper; + + @Autowired + private DeviceMapper deviceMapper; + + @Autowired + private SysSipConfig sysSipConfig; + + @Autowired + private RedisCache redisCache; + + @Override + public void updateDefaultSipConfig(SipConfig sipConfig) { + SysSipConfig defConfig = new SysSipConfig(); + defConfig.setEnabled(sipConfig.getEnabled()==1); + defConfig.setIp(sipConfig.getIp()); + defConfig.setPort(sipConfig.getPort()); + defConfig.setDomain(sipConfig.getDomain()); + defConfig.setId(sipConfig.getServerSipid()); + defConfig.setPassword(sipConfig.getPassword()); + redisCache.setCacheObject(FastBeeConstant.REDIS.DEFAULT_SIP_CONFIG, defConfig); + } + + @Override + public SipConfig GetDefaultSipConfig() { + Object temp = redisCache.getCacheObject(FastBeeConstant.REDIS.DEFAULT_SIP_CONFIG); + SipConfig sipConfig = new SipConfig(); + if (temp == null) { + sipConfig.setEnabled(sysSipConfig.isEnabled() ? 1 : 0); + sipConfig.setIp(sysSipConfig.getIp()); + sipConfig.setPort(sysSipConfig.getPort()); + sipConfig.setDomain(sysSipConfig.getDomain()); + sipConfig.setServerSipid(sysSipConfig.getId()); + sipConfig.setPassword(sysSipConfig.getPassword()); + redisCache.setCacheObject(FastBeeConstant.REDIS.DEFAULT_SIP_CONFIG, temp); + } else if (temp instanceof SipConfig){ + sipConfig = (SipConfig) temp; + updateDefaultSipConfig((SipConfig) temp); + } else if (temp instanceof SysSipConfig){ + SysSipConfig temp2 = (SysSipConfig) temp; + sipConfig.setEnabled(temp2.isEnabled() ? 1 : 0); + sipConfig.setIp(temp2.getIp()); + sipConfig.setPort(temp2.getPort()); + sipConfig.setDomain(temp2.getDomain()); + sipConfig.setServerSipid(temp2.getId()); + sipConfig.setPassword(temp2.getPassword()); + } + return sipConfig; + } + + /** + * 查询产品下第一条sip系统配置 + * + * @return sip系统配置 + */ + @Override + public SipConfig selectSipConfigByProductId(Long productId) { + return sipConfigMapper.selectSipConfigByProductId(productId); + } + + + @Override + public SipConfig selectSipConfigBydeviceSipId(String deviceSipId) { + Device device = deviceMapper.selectDeviceBySerialNumber(deviceSipId); + if (device != null) { + return sipConfigMapper.selectSipConfigByProductId(device.getProductId()); + } else { + return this.GetDefaultSipConfig(); + } + } + + /** + * 新增sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + @Override + public int insertSipConfig(SipConfig sipConfig) { + sipConfig.setCreateTime(DateUtils.getNowDate()); + if (sipConfig.getIsdefault() != null && sipConfig.getIsdefault() == 1) { + sipConfigMapper.resetDefaultSipConfig(); + updateDefaultSipConfig(sipConfig); + } + return sipConfigMapper.insertSipConfig(sipConfig); + } + + /** + * 修改sip系统配置 + * + * @param sipConfig sip系统配置 + * @return 结果 + */ + @Override + public int updateSipConfig(SipConfig sipConfig) { + sipConfig.setUpdateTime(DateUtils.getNowDate()); + if (sipConfig.getIsdefault() != null && sipConfig.getIsdefault() == 1) { + sipConfigMapper.resetDefaultSipConfig(); + updateDefaultSipConfig(sipConfig); + } + return sipConfigMapper.updateSipConfig(sipConfig); + } + + @Override + public void syncSipConfig(SysSipConfig sipConfig) { + List list = sipConfigMapper.selectSipConfigList(new SipConfig()); + for (SipConfig config : list) { + config.setIp(sipConfig.getIp()); + config.setPort(sipConfig.getPort()); + sipConfigMapper.updateSipConfig(config); + } + GetDefaultSipConfig(); + } + + /** + * 批量删除sip系统配置 + * + * @param ids 需要删除的sip系统配置主键 + * @return 结果 + */ + @Override + public int deleteSipConfigByIds(Long[] ids) { + return sipConfigMapper.deleteSipConfigByIds(ids); + } + + @Override + public int deleteSipConfigByProductIds(Long[] productIds) { + return sipConfigMapper.deleteSipConfigByProductId(productIds); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceChannelServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceChannelServiceImpl.java new file mode 100644 index 00000000..6a7bbbaa --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceChannelServiceImpl.java @@ -0,0 +1,334 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.service.IProductService; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.enums.DeviceChannelStatus; +import com.fastbee.sip.mapper.SipDeviceChannelMapper; +import com.fastbee.sip.mapper.SipDeviceMapper; +import com.fastbee.sip.model.BaseTree; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.server.VideoSessionManager; +import com.fastbee.sip.service.ISipDeviceChannelService; +import com.fastbee.sip.util.SipUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 监控设备通道信息Service业务层处理 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Service +public class SipDeviceChannelServiceImpl implements ISipDeviceChannelService { + @Autowired + private SipDeviceChannelMapper sipDeviceChannelMapper; + + @Autowired + private RedisCache redisCache; + + @Autowired + private IProductService productService; + + @Autowired + private SipDeviceMapper sipDeviceMapper; + + @Autowired + private VideoSessionManager streamSession; + + @Override + public void updateChannel(String deviceId, SipDeviceChannel channel) { + SipDeviceChannel deviceChannel = sipDeviceChannelMapper.selectSipDeviceChannelByChannelSipId(channel.getChannelSipId()); + if (deviceChannel == null) { + insertSipDeviceChannel(channel); + } else { + channel.setId(deviceChannel.getId()); + sipDeviceChannelMapper.updateSipDeviceChannel(channel); + } + } + + /** + * 查询监控设备通道信息 + * + * @param channelId 监控设备通道信息主键 + * @return 监控设备通道信息 + */ + @Override + public SipDeviceChannel selectSipDeviceChannelByChannelId(Long channelId) { + return sipDeviceChannelMapper.selectSipDeviceChannelById(channelId); + } + + @Override + public SipDeviceChannel selectSipDeviceChannelByChannelSipId(String channelSipId) { + return sipDeviceChannelMapper.selectSipDeviceChannelByChannelSipId(channelSipId); + } + + @Override + public List selectSipDeviceChannelByDeviceSipId(String deviceSipId) { + return sipDeviceChannelMapper.selectSipDeviceChannelByDeviceSipId(deviceSipId); + } + + /** + * 查询监控设备通道信息列表 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 监控设备通道信息 + */ + @Override + public List selectSipDeviceChannelList(SipDeviceChannel sipDeviceChannel) { + SysUser user = getLoginUser().getUser(); +// List roles=user.getRoles(); + // 租户 +// if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ +// sipDeviceChannel.setTenantId(user.getUserId()); +// } + sipDeviceChannel.setTenantId(user.getUserId()); + List list = sipDeviceChannelMapper.selectSipDeviceChannelList(sipDeviceChannel); + List newList = new ArrayList<>(); + if (list != null && list.size() > 0) { + for (SipDeviceChannel channel : list) { + newList.add(updateChannelStatus(channel)); + } + } + return newList; + } + + public SipDeviceChannel updateChannelStatus(SipDeviceChannel channel) { + String streamid = String.format("gb_play_%s_%s", channel.getDeviceSipId(), channel.getChannelSipId()); + VideoSessionInfo sinfo = streamSession.getSessionInfo(channel.getDeviceSipId(), + channel.getChannelSipId(), streamid,"play"); + if (sinfo != null) { + channel.setStreamPush(sinfo.isPushing() ? 1 : 0); + channel.setStreamRecord(sinfo.isRecording() ? 1 : 0); + channel.setVideoRecord(sinfo.isVideoRecord() ? 1 : 0); + } + return channel; + } + + /** + * 新增监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + @Override + public String insertSipDeviceChannelGen(Long createNum, SipDeviceChannel sipDeviceChannel) { + int ret = 0; + int devuserid = 0; + int channeluserid = 0; + String tmpdev = sipDeviceChannel.getDeviceSipId(); + String tmpchannel = sipDeviceChannel.getChannelSipId(); + Object cacheObj = redisCache.getCacheObject(RedisKeyBuilder.buildSipDeviceidCacheKey(tmpdev)); + if (cacheObj != null) { + devuserid = (int) cacheObj; + } + cacheObj = redisCache.getCacheObject(RedisKeyBuilder.buildSipDeviceidCacheKey(tmpchannel)); + if (cacheObj != null) { + channeluserid = (int) cacheObj; + } + String devstr = String.format("%06d", devuserid + 1); + sipDeviceChannel.setDeviceSipId(tmpdev + devstr); + sipDeviceChannel.setStatus(DeviceChannelStatus.notused.getValue()); + //限制最大添加数量为10 + if (createNum > 10) { + createNum = 10L; + } + for (int i = 1; i <= createNum; i++) { + String channelstr = String.format("%06d", channeluserid + i); + sipDeviceChannel.setChannelSipId(tmpchannel + channelstr); + SipDeviceChannel deviceChannel = sipDeviceChannelMapper.selectSipDeviceChannelByChannelSipId(sipDeviceChannel.getChannelSipId()); + if (deviceChannel == null) { + ret = insertSipDeviceChannel(sipDeviceChannel); + } + } + redisCache.incr(RedisKeyBuilder.buildSipDeviceidCacheKey(tmpdev), 1); + redisCache.incr(RedisKeyBuilder.buildSipDeviceidCacheKey(tmpchannel), createNum); + if (ret > 0) { + return tmpdev + devstr; + } else { + return ""; + } + } + + @Override + public int insertSipDeviceChannel(SipDeviceChannel sipDeviceChannel) { + Product product = productService.getProductBySerialNumber(sipDeviceChannel.getDeviceSipId()); + if (product != null) { + sipDeviceChannel.setTenantId(product.getTenantId()); + sipDeviceChannel.setTenantName(product.getTenantName()); + sipDeviceChannel.setProductId(product.getProductId()); + sipDeviceChannel.setProductName(product.getProductName()); + } + sipDeviceChannel.setCreateTime(DateUtils.getNowDate()); + return sipDeviceChannelMapper.insertSipDeviceChannel(sipDeviceChannel); + } + + /** + * 修改监控设备通道信息 + * + * @param sipDeviceChannel 监控设备通道信息 + * @return 结果 + */ + @Override + public int updateSipDeviceChannel(SipDeviceChannel sipDeviceChannel) + { + return sipDeviceChannelMapper.updateSipDeviceChannel(sipDeviceChannel); + } + + @Override + public int updateSipDeviceChannelStatus(String ChannelId, Integer status) { + SipDeviceChannel sipDeviceChannel = sipDeviceChannelMapper.selectSipDeviceChannelByChannelSipId(ChannelId); + if (sipDeviceChannel != null) { + if (sipDeviceChannel.getRegisterTime() == null && status == 2) { + sipDeviceChannel.setRegisterTime(DateUtils.getNowDate()); + } + sipDeviceChannel.setStatus(status); + return sipDeviceChannelMapper.updateSipDeviceChannel(sipDeviceChannel); + } + return 0; + } + + /** + * 批量删除监控设备通道信息 + * + * @param channelIds 需要删除的监控设备通道信息主键 + * @return 结果 + */ + @Override + public int deleteSipDeviceChannelByChannelIds(Long[] channelIds) + { + return sipDeviceChannelMapper.deleteSipDeviceChannelByIds(channelIds); + } + + /** + * 删除监控设备通道信息信息 + * + * @param channelId 监控设备通道信息主键 + * @return 结果 + */ + @Override + public int deleteSipDeviceChannelByChannelId(Long channelId) + { + return sipDeviceChannelMapper.deleteSipDeviceChannelById(channelId); + } + + @Override + public List> queryVideoDeviceTree(String deviceId, String parentId, boolean onlyCatalog) { + SipDevice device = sipDeviceMapper.selectSipDeviceBySipId(deviceId); + if (device == null) { + return null; + } + if (parentId == null || parentId.equals(deviceId)) { + // 字根节点开始查询 + List rootNodes = getRootNodes(deviceId, true, !onlyCatalog); + return transportChannelsToTree(rootNodes, ""); + } + + if (parentId.length()%2 != 0) { + return null; + } + if (parentId.length() == 10 ) { + if (onlyCatalog) { + return null; + } + // parentId为行业编码, 其下不会再有行政区划 + List channels = sipDeviceChannelMapper.selectChannelByCivilCode(deviceId, parentId); + return transportChannelsToTree(channels, parentId); + } + // 查询其下的行政区划和摄像机 + List channelsForCivilCode = sipDeviceChannelMapper.selectChannelWithCivilCodeAndLength(deviceId, parentId, parentId.length() + 2); + if (!onlyCatalog) { + List channels = sipDeviceChannelMapper.selectChannelByCivilCode(deviceId, parentId); + + for(SipDeviceChannel channel : channels) { + boolean flag = false; + for(SipDeviceChannel deviceChannel : channelsForCivilCode) { + if(channel.getChannelSipId().equals(deviceChannel.getChannelSipId())) { + flag = true; + } + } + if(!flag) { + channelsForCivilCode.add(channel); + } + } + } + return transportChannelsToTree(channelsForCivilCode, parentId); + } + + private List getRootNodes(String deviceId, boolean haveCatalog, boolean haveChannel) { + if (!haveCatalog && !haveChannel) { + return null; + } + List result = new ArrayList<>(); + // 使用行政区划 + Integer length = sipDeviceChannelMapper.getChannelMinLength(deviceId); + if (length == null) { + return null; + } + if (length <= 10) { + if (haveCatalog) { + List provinceNode = sipDeviceChannelMapper.selectChannelWithCivilCodeAndLength(deviceId, null, length); + if (provinceNode != null && provinceNode.size() > 0) { + result.addAll(provinceNode); + } + } + if (haveChannel) { + // 查询那些civilCode不在通道中的不规范通道,放置在根目录 + List nonstandardNode = sipDeviceChannelMapper.selectChannelWithoutCiviCode(deviceId); + if (nonstandardNode != null && nonstandardNode.size() > 0) { + result.addAll(nonstandardNode); + } + } + }else { + if (haveChannel) { + List deviceChannels = sipDeviceChannelMapper.selectSipDeviceChannelByDeviceSipId(deviceId); + if (deviceChannels != null && deviceChannels.size() > 0) { + result.addAll(deviceChannels); + } + } + } + return result; + } + + private List> transportChannelsToTree(List channels, String parentId) { + if (channels == null) { + return null; + } + List> treeNotes = new ArrayList<>(); + if (channels.size() == 0) { + return treeNotes; + } + for (SipDeviceChannel channel : channels) { + BaseTree node = new BaseTree<>(); + node.setId(channel.getChannelSipId()); + node.setDeviceId(channel.getDeviceSipId()); + node.setName(channel.getChannelName()); + node.setPid(parentId); + node.setBasicData(channel); + node.setParent(false); + if (channel.getChannelSipId().length() > 8) { + if (channel.getChannelSipId().length() > 13) { + String gbCodeType = channel.getChannelSipId().substring(10, 13); + node.setParent(gbCodeType.equals(SipUtil.BUSINESS_GROUP) || gbCodeType.equals(SipUtil.VIRTUAL_ORGANIZATION) ); + } + } else { + node.setParent(true); + } + treeNotes.add(node); + } + Collections.sort(treeNotes); + return treeNotes; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceServiceImpl.java new file mode 100644 index 00000000..3673f55e --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/SipDeviceServiceImpl.java @@ -0,0 +1,152 @@ +package com.fastbee.sip.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.mapper.SipDeviceChannelMapper; +import com.fastbee.sip.mapper.SipDeviceMapper; +import com.fastbee.sip.model.BaseTree; +import com.fastbee.sip.service.ISipDeviceChannelService; +import com.fastbee.sip.service.ISipDeviceService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 监控设备Service业务层处理 + * + * @author zhuangpeng.li + * @date 2022-10-07 + */ +@Service +@Slf4j +public class SipDeviceServiceImpl implements ISipDeviceService +{ + @Autowired + private SipDeviceMapper sipDeviceMapper; + + @Autowired + private SipDeviceChannelMapper sipDeviceChannelMapper; + + @Autowired + private ISipDeviceChannelService sipDeviceChannelService; + + @Autowired + private IDeviceService deviceService; + + @Override + public boolean exists(String sipId) { + return sipDeviceMapper.selectSipDeviceBySipId(sipId) != null; + } + + @Override + public boolean updateDevice(SipDevice device) { + SipDevice devicetemp = sipDeviceMapper.selectSipDeviceBySipId(device.getDeviceSipId()); + if (devicetemp == null) { + return sipDeviceMapper.insertSipDevice(device) > 0; + }else { + device.setDeviceId(devicetemp.getDeviceId()); + return sipDeviceMapper.updateSipDevice(device) > 0; + } + } + + /** + * 查询监控设备 + * + * @param deviceId 监控设备主键 + * @return 监控设备 + */ + @Override + public SipDevice selectSipDeviceByDeviceId(Long deviceId) + { + return sipDeviceMapper.selectSipDeviceByDeviceId(deviceId); + } + + @Override + public SipDevice selectSipDeviceBySipId(String sipId) + { + return sipDeviceMapper.selectSipDeviceBySipId(sipId); + } + /** + * 查询监控设备列表 + * + * @param sipDevice 监控设备 + * @return 监控设备 + */ + @Override + public List selectSipDeviceList(SipDevice sipDevice) + { + return sipDeviceMapper.selectSipDeviceList(sipDevice); + } + + @Override + public List> selectSipDeviceChannelList(String deviceId) { + return sipDeviceChannelService.queryVideoDeviceTree(deviceId,null,false); + } + + /** + * 新增监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + @Override + public int insertSipDevice(SipDevice sipDevice) + { + sipDevice.setCreateTime(DateUtils.getNowDate()); + return sipDeviceMapper.insertSipDevice(sipDevice); + } + + /** + * 修改监控设备 + * + * @param sipDevice 监控设备 + * @return 结果 + */ + @Override + public int updateSipDevice(SipDevice sipDevice) + { + sipDevice.setUpdateTime(DateUtils.getNowDate()); + return sipDeviceMapper.updateSipDevice(sipDevice); + } + + @Override + public int updateSipDeviceStatus(SipDevice sipDevice) { + sipDevice.setUpdateTime(DateUtils.getNowDate()); + return sipDeviceMapper.updateSipDeviceStatus(sipDevice); + } + + /** + * 批量删除监控设备 + * + * @param deviceIds 需要删除的监控设备主键 + * @return 结果 + */ + @Override + public int deleteSipDeviceByDeviceIds(Long[] deviceIds) + { + return sipDeviceMapper.deleteSipDeviceByDeviceIds(deviceIds); + } + + /** + * 删除监控设备信息 + * + * @param deviceId 监控设备主键 + * @return 结果 + */ + @Override + public int deleteSipDeviceByDeviceId(String deviceId) + { + return sipDeviceMapper.deleteSipDeviceByDeviceId(Long.valueOf(deviceId)); + } + + @Override + public int deleteSipDeviceBySipId(String SipId) + { + sipDeviceMapper.deleteSipDeviceByByDeviceSipId(SipId); + return sipDeviceChannelMapper.deleteSipDeviceChannelByDeviceId(SipId); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/VideoMqttService.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/VideoMqttService.java new file mode 100644 index 00000000..53704ca7 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/VideoMqttService.java @@ -0,0 +1,200 @@ +package com.fastbee.sip.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.mapper.DeviceMapper; + +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.sip.domain.SipConfig; +import com.fastbee.sip.domain.SipDevice; +import com.fastbee.sip.domain.SipDeviceChannel; +import com.fastbee.sip.model.RecordList; +import com.fastbee.sip.model.SipDeviceSummary; +import com.fastbee.sip.server.msg.Alarm; +import com.fastbee.sip.service.IMqttService; +import com.fastbee.sip.service.ISipConfigService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + + +@Slf4j +@Service +public class VideoMqttService implements IMqttService { + @Resource + private PubMqttClient mqttClient; + + @Autowired + private ISipConfigService sipConfigService; + + @Autowired + private DeviceMapper deviceMapper; + + @Resource + private TopicsUtils topicsUtils; + + @Override + public void publishInfo(SipDevice device) { + // wifi信号:rssi + // 固件版本,firmwareVersion + // 设备状态,status // (1-未激活,2-禁用,3-在线,4-离线) + // 用户Id,userId + // 经度,longitude + // 纬度,latitude + // 设备摘要,可选(自定义配置信息)summary + //默认获取admin账号配置 + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (null != sipConfig) { + Device iotdevice = deviceMapper.selectDeviceBySerialNumber(device.getDeviceSipId()); + if (iotdevice == null ) { + iotdevice = new Device(); + iotdevice.setSerialNumber(device.getDeviceSipId()); + iotdevice.setDeviceName(device.getDeviceName()); + } + iotdevice.setProductId(sipConfig.getProductId()); + iotdevice.setProductName(device.getModel()); + + iotdevice.setRssi(0); + iotdevice.setStatus(3); + iotdevice.setFirmwareVersion(BigDecimal.valueOf(1.0)); + iotdevice.setNetworkIp(device.getIp()); + deviceMapper.updateDevice(iotdevice); + SipDeviceSummary deviceSummary = new SipDeviceSummary(device); + iotdevice.setSummary(JSON.toJSONString(deviceSummary)); + Long productId = sipConfig.getProductId(); + if (null != productId && productId != -1L && productId != 0L) { + String topic = topicsUtils.buildTopic(productId, device.getDeviceSipId(), TopicType.DEV_INFO_POST); + mqttClient.publish(1, false, topic, JSON.toJSONString(iotdevice)); + } + } + } + + @Override + public void publishStatus(SipDevice device, int deviceStatus) { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(device.getDeviceSipId()); + if (null != sipConfig) { + Long productId = sipConfig.getProductId(); + if (null != productId && productId != -1L && productId != 0L) { + int isShadow = 0; + int rssi = 0; + String message = "{\"status\":" + deviceStatus + ",\"isShadow\":" + isShadow + ",\"rssi\":" + rssi + "}"; + String topic = topicsUtils.buildTopic(sipConfig.getProductId(), device.getDeviceSipId(), TopicType.STATUS_POST); + log.info("topic:{} ",topic); + mqttClient.publish(1, false, topic, message); + //更新数据库 + Device dev = deviceMapper.selectDeviceBySerialNumber(device.getDeviceSipId()); + if (dev != null && dev.getStatus() != deviceStatus) { + dev.setStatus(deviceStatus); + deviceMapper.updateDeviceStatus(dev); + } + } + } + } + + @Override + public void publishEvent(Alarm alarm) { + Alarm.Info info; + ThingsModelSimpleItem item; + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(alarm.getDeviceId()); + if (null != sipConfig) { + List events = new ArrayList<>(); + switch (alarm.getAlarmMethod()){ + case telAlarm: + item = new ThingsModelSimpleItem("telAlarm","1",""); + events.add(item); + break; + case devAlarm: + item = new ThingsModelSimpleItem("devAlarm","1",""); + info = alarm.getInfo(); + if(info != null && info.getAlarmType() != null){ + item.setValue(info.getAlarmType()); + } + events.add(item); + break; + case smsAlarm: + item = new ThingsModelSimpleItem("smsAlarm","1",""); + events.add(item); + break; + case gpsAlarm: + item = new ThingsModelSimpleItem("gpsAlarm","1",""); + events.add(item); + break; + case videoAlarm: + item = new ThingsModelSimpleItem("videoAlarm","1",""); + info = alarm.getInfo(); + if(info != null && info.getAlarmType() != null){ + item.setValue(info.getAlarmType()); + } + events.add(item); + break; + case devErrorAlarm: + item = new ThingsModelSimpleItem("devErrorAlarm","1",""); + info = alarm.getInfo(); + if(info != null && info.getAlarmType() != null){ + item.setValue(info.getAlarmType()); + } + events.add(item); + break; + case other: + item = new ThingsModelSimpleItem("otherAlarm","1",""); + events.add(item); + break; + } + Long productId = sipConfig.getProductId(); + if (null != productId && productId != -1L && productId != 0L) { + String topic = topicsUtils.buildTopic(sipConfig.getProductId(), alarm.getDeviceId(), TopicType.DEV_EVENT_POST); + mqttClient.publish(1, false, topic, JSON.toJSONString(events)); + } + } + } + + public void publishChannelsProperty(String DeviceSipId, List channels) { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(DeviceSipId); + if (null != sipConfig) { + List thingsList = new ArrayList<>(); + ThingsModelSimpleItem item = new ThingsModelSimpleItem(); + item.setId("channel"); + item.setValue(JSON.toJSONString(channels)); + thingsList.add(item); + publishProperty(sipConfig.getProductId(), DeviceSipId, thingsList, 0); + } + } + + @Override + public void publishRecordsProperty(String DeviceSipId, RecordList recordList) { + SipConfig sipConfig = sipConfigService.selectSipConfigBydeviceSipId(DeviceSipId); + if (null != sipConfig) { + List thingsList = new ArrayList<>(); + ThingsModelSimpleItem item = new ThingsModelSimpleItem(); + item.setId("recordList"); + item.setValue(JSON.toJSONString(recordList)); + thingsList.add(item); + publishProperty(sipConfig.getProductId(), DeviceSipId, thingsList, 0); + } + } + + public void publishProperty(Long productId, String deviceNum, List thingsList, int delay) { + String pre = ""; + if (delay > 0) { + pre = "$delayed/" + String.valueOf(delay) + "/"; + } + if (null != productId && productId != -1L && productId != 0L) { + String topic = topicsUtils.buildTopic(productId, deviceNum, TopicType.DEV_PROPERTY_POST); + if (thingsList == null) { + mqttClient.publish(1, false, topic, ""); + } else { + mqttClient.publish(1, false, topic, JSON.toJSONString(thingsList)); + } + } + } + + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/ZmlHookServiceImpl.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/ZmlHookServiceImpl.java new file mode 100644 index 00000000..06f73c9b --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/service/impl/ZmlHookServiceImpl.java @@ -0,0 +1,336 @@ +package com.fastbee.sip.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.enums.SessionType; +import com.fastbee.sip.model.Stream; +import com.fastbee.sip.model.VideoSessionInfo; +import com.fastbee.sip.server.ISipCmd; +import com.fastbee.sip.server.VideoSessionManager; +import com.fastbee.sip.service.IMediaServerService; +import com.fastbee.sip.service.IZmlHookService; +import com.fastbee.sip.util.ZlmApiUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +@Slf4j +@Service +public class ZmlHookServiceImpl implements IZmlHookService { + + @Autowired + private VideoSessionManager videoSessionManager; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private ISipCmd sipCmd; + + @Autowired + private ZlmApiUtils zlmApiUtils; + + @Override + public JSONObject onHttpAccess(JSONObject json) { + log.warn("on_http_access:" + json.toString()); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("err", ""); + ret.put("path", ""); + ret.put("second", 600); + return ret; + } + + @Override + public JSONObject onPlay(JSONObject json) { + String mediaServerId = json.getString("mediaServerId"); + String id = json.getString("id"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + log.warn("[ZLM HOOK] 播放鉴权, {} {}->{}->{}/{}", mediaServerId, id, schema, app, streamId); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + VideoSessionInfo sinfo = videoSessionManager.getSessionInfoBySSRC(streamId); + if (sinfo != null) { + if (sinfo.isPushing()) { + int time = sinfo.getOnPlaytime() + 1; + sinfo.setOnPlaytime(time); + } + videoSessionManager.put(sinfo, null); + } + return ret; + } + + @Override + public JSONObject onPublish(JSONObject json) { + log.info("onPublish:{}", json); + String id = json.getString("id"); + String mediaServerId = json.getString("mediaServerId"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + JSONObject ret = new JSONObject(); + VideoSessionInfo sinfo = videoSessionManager.getSessionInfoBySSRC(streamId); + if (sinfo != null) { + log.warn("[ZLM HOOK] 推流鉴权, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + ret.put("code", 0); + ret.put("msg", "success"); + switch (sinfo.getType()) { + case play: + ret.put("enable_fmp4", true); + ret.put("enable_hls", true); + ret.put("enable_rtmp", true); + ret.put("enable_rtsp", true); + break; + case playrecord: + ret.put("enable_rtmp", true); + ret.put("enable_hls", true); + ret.put("enable_mp4", true); + ret.put("enable_audio", true); + ret.put("mp4_max_second", 30); + ret.put("mp4_as_player", true); + ret.put("mp4_save_path", "/opt/media/bin/www"); + sinfo.setRecording(true); + break; + case playback: + ret.put("enable_fmp4", true); + ret.put("enable_hls", true); + ret.put("enable_rtmp", true); + break; + case download: + //开启录像,默认60s + ret.put("enable_mp4", true); + ret.put("enable_audio", true); + ret.put("mp4_max_second", 30); + ret.put("mp4_save_path", "/opt/media/bin/www"); + break; + } + sinfo.setPushing(true); + videoSessionManager.put(sinfo, null); + } else { + log.warn("[ZLM HOOK] 推流鉴权失败, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + ret.put("code", 401); + ret.put("msg", "Unauthorized"); + } + return ret; + } + + @Override + public JSONObject onStreamNoneReader(JSONObject json) { + String mediaServerId = json.getString("mediaServerId"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + log.warn("[ZLM HOOK] 流无人观看, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + JSONObject ret = new JSONObject(); + VideoSessionInfo sinfo = videoSessionManager.getSessionInfoBySSRC(streamId); + if (sinfo != null) { + ret.put("code", 0); + switch (sinfo.getType()) { + case playrecord: + case download: + ret.put("close", false); + break; + default: + ret.put("close", true); + } + } + return ret; + } + + @Override + public JSONObject onStreamNotFound(JSONObject json) { + String id = json.getString("id"); + String mediaServerId = json.getString("mediaServerId"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + log.warn("[ZLM HOOK] 流未找到, {} {}->{}->{}/{}", mediaServerId, id, schema, app, streamId); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onStreamChanged(JSONObject json) { + log.info("onStreamChanged:{}", json); + boolean regist = json.getBoolean("regist"); + String mediaServerId = json.getString("mediaServerId"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + VideoSessionInfo sinfo = videoSessionManager.getSessionInfoBySSRC(streamId); + if (sinfo != null) { + if (regist) { + log.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + if (schema.equals("rtsp") && sinfo.getType() == SessionType.play) { + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(sinfo.getDeviceId()); + String streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaInfo.getPortRtsp(), "rtp", sinfo.getStream()); + String fileName = sinfo.getDeviceId() + "_" + sinfo.getChannelId() + ".jpg"; + // 请求截图 + log.info("[请求截图]: {}:{}", streamUrl, fileName); + zlmApiUtils.getSnap(mediaInfo, streamUrl, 15, 1, fileName); + } + sinfo.setPushing(true); + } else { + log.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + if (sinfo != null ) { + if (sinfo.isPushing()) { + sinfo.setPushing(false); + } + if(sinfo.isRecording()) { + sinfo.setRecording(false); + } + } + } + videoSessionManager.put(sinfo, null); + } + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onFlowReport(JSONObject json) { + String id = json.getString("id"); + String mediaServerId = json.getString("mediaServerId"); + String schema = json.getString("schema"); + String app = json.getString("app"); + String streamId = json.getString("stream"); + int duration = json.getInteger("duration"); + int totalBytes = json.getInteger("totalBytes"); + boolean player = json.getBoolean("player"); + VideoSessionInfo sinfo = videoSessionManager.getSessionInfoBySSRC(streamId); + if (sinfo != null) { + if (player) { + int time = sinfo.getOnPlaytime() - 1; + sinfo.setOnPlaytime(time); + log.info("[ZLM HOOK] 播放器断开,流量统计事件, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + } else { + log.info("[ZLM HOOK] 推流器断开,流量统计事件, {}->{}->{}/{}", mediaServerId, schema, app, streamId); + sinfo.setPushing(false); + sinfo.setPlayer(0); + sinfo.setOnPlaytime(0); + } + videoSessionManager.put(sinfo, null); + } + log.info("[ZLM HOOK] onFlowReport:维持时间:{}/上下行流量:{}", duration, totalBytes); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + + @Override + public JSONObject onRtpServerTimeout(JSONObject json) { + String mediaServerId = json.getString("mediaServerId"); + String stream_id = json.getString("stream_id"); + String ssrc = json.getString("ssrc"); + log.warn("[ZLM HOOK] rtpServer收流超时:{}->{}({})", mediaServerId, stream_id, ssrc); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onSendRtpStopped(JSONObject json) { + log.warn("[ZLM HOOK] rtp发送停止回调:{}", json); + String mediaServerId = json.getString("mediaServerId"); + String stream_id = json.getString("stream_id"); + String ssrc = json.getString("ssrc"); + log.warn("[ZLM HOOK] rtp发送停止回调:{}->{}({})", mediaServerId, stream_id, ssrc); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onRecordMp4(JSONObject json) { + String mediaServerId = json.getString("mediaServerId"); + String file_path = json.getString("file_path"); + String app = json.getString("app"); + String stream = json.getString("stream"); + log.info("[ZLM HOOK] 录制完成:{}->{} {} ({})", mediaServerId, app, stream, file_path); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onServerStarted(JSONObject json) { + log.info("[ZLM HOOK] 流媒体服务启动成功:({})", json); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onServerKeepalive(JSONObject json) { + log.debug("[ZLM HOOK] 流媒体服务心跳:({})", json); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + @Override + public JSONObject onServerExited(JSONObject json) { + log.info("[ZLM HOOK] 流媒体服务存活:({})", json); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return ret; + } + + public Stream updateStream(VideoSessionInfo sInfo) { + if(sInfo == null){ + log.error("updateStream sInfo is null"); + return null; + } + String streamId = sInfo.getStream(); + String ssrc = sInfo.getSsrc(); + MediaServer mediaInfo = mediaServerService.selectMediaServerBydeviceSipId(sInfo.getDeviceId()); + Stream streamUrl = new Stream(sInfo.getDeviceId(), sInfo.getChannelId(), streamId, ssrc); + String server = Objects.equals(mediaInfo.getDomain(), "") ? mediaInfo.getIp() : mediaInfo.getDomain(); + streamUrl.setFlv(String.format("http://%s:%s/rtp/%s.live.flv", mediaInfo.getIp(), + mediaInfo.getPortHttp(), streamId)); + streamUrl.setHttps_flv(String.format("https://%s:%s/rtp/%s.live.flv", server, mediaInfo.getPortHttps(), streamId)); + streamUrl.setWs_flv(String.format("ws://%s:%s/rtp/%s.live.flv", mediaInfo.getIp(), + mediaInfo.getPortHttps(), streamId)); + streamUrl.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getIp(), + mediaInfo.getPortRtmp(), streamId)); + streamUrl.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getIp(), + mediaInfo.getPortRtsp(), streamId)); + streamUrl.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getIp(), + mediaInfo.getPortHttp(), streamId)); + streamUrl.setHttps_fmp4(String.format("https://%s:%s/rtp/%s.live.mp4", mediaInfo.getIp(), + mediaInfo.getPortHttps(), streamId)); + streamUrl.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getIp(), mediaInfo.getPortHttp(), streamId)); + streamUrl.setHttps_hls(String.format("https://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getIp(), mediaInfo.getPortHttp(), streamId)); + + if (Objects.equals(mediaInfo.getProtocol(), "http")) { + streamUrl.setPlayurl(streamUrl.getFlv()); + } else if (Objects.equals(mediaInfo.getProtocol(), "https")) { + streamUrl.setPlayurl(streamUrl.getHttps_flv()); + } else if (Objects.equals(mediaInfo.getProtocol(), "ws")) { + streamUrl.setPlayurl(streamUrl.getWs_flv()); + } else if (Objects.equals(mediaInfo.getProtocol(), "rtmp")) { + streamUrl.setPlayurl(streamUrl.getRtmp()); + } else if (Objects.equals(mediaInfo.getProtocol(), "rtsp")) { + streamUrl.setPlayurl(streamUrl.getRtsp()); + } + + return streamUrl; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/DigestAuthUtil.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/DigestAuthUtil.java new file mode 100644 index 00000000..40fe9569 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/DigestAuthUtil.java @@ -0,0 +1,185 @@ +package com.fastbee.sip.util; + +import gov.nist.core.InternalErrorHandler; +import lombok.extern.slf4j.Slf4j; + +import javax.sip.address.URI; +import javax.sip.header.AuthorizationHeader; +import javax.sip.header.HeaderFactory; +import javax.sip.header.WWWAuthenticateHeader; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.DecimalFormat; +import java.util.Date; +import java.util.Random; + +/** + * Implements the HTTP digest authentication method server side functionality. + * + * @author M. Ranganathan + * @author Marc Bednarek + */ + +@Slf4j +public class DigestAuthUtil{ + + private final MessageDigest messageDigest; + public static final String DEFAULT_ALGORITHM = "MD5"; + public static final String DEFAULT_SCHEME = "Digest"; + /** to hex converter */ + private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + /** + * Default constructor. + * @throws NoSuchAlgorithmException + */ + public DigestAuthUtil() + throws NoSuchAlgorithmException { + messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); + } + + public static String toHexString(byte b[]) { + int pos = 0; + char[] c = new char[b.length * 2]; + for (byte value : b) { + c[pos++] = toHex[(value >> 4) & 0x0F]; + c[pos++] = toHex[value & 0x0f]; + } + return new String(c); + } + + /** + * Generate the challenge string. + * + * @return a generated nonce. + */ + private String generateNonce() { + // Get the time of day and run MD5 over it. + Date date = new Date(); + long time = date.getTime(); + Random rand = new Random(); + long pad = rand.nextLong(); + String nonceString = (new Long(time)).toString() + + (new Long(pad)).toString(); + byte[] mdbytes = messageDigest.digest(nonceString.getBytes()); + // Convert the mdbytes array into a hex string. + return toHexString(mdbytes); + } + + public Response generateChallenge(HeaderFactory headerFactory, Response response, String realm) { + try { + WWWAuthenticateHeader proxyAuthenticate = headerFactory + .createWWWAuthenticateHeader(DEFAULT_SCHEME); + proxyAuthenticate.setParameter("realm", realm); + proxyAuthenticate.setParameter("nonce", generateNonce()); + proxyAuthenticate.setParameter("opaque", ""); + proxyAuthenticate.setParameter("stale", "FALSE"); + proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM); + + response.setHeader(proxyAuthenticate); + } catch (Exception ex) { + InternalErrorHandler.handleException(ex); + } + return response; + } + /** + * Authenticate the inbound request. + * + * @param request - the request to authenticate. + * @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password. + * + * @return true if authentication succeded and false otherwise. + */ + public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) { + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + if ( authHeader == null ) return false; + String realm = authHeader.getRealm(); + String username = authHeader.getUsername(); + + if ( username == null || realm == null ) { + return false; + } + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return false; + } + + String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); + + byte[] mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + + String cnonce = authHeader.getCNonce(); + String KD = hashedPassword + ":" + nonce; + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + HA2; + mdbytes = messageDigest.digest(KD.getBytes()); + String mdString = toHexString(mdbytes); + String response = authHeader.getResponse(); + + + return mdString.equals(response); + } + + /** + * Authenticate the inbound request given plain text password. + * + * @param request - the request to authenticate. + * @param pass -- the plain text password. + * + * @return true if authentication succeded and false otherwise. + */ + public boolean doAuthenticatePlainTextPassword(Request request, String pass) { + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + if ( authHeader == null ) return false; + String realm = authHeader.getRealm().trim(); + String username = authHeader.getUsername().trim(); + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return false; + } + // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 + String qop = authHeader.getQop(); + + // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量 + int nc = authHeader.getNonceCount(); + String ncStr = new DecimalFormat("00000000").format(nc); + + String A1 = username + ":" + realm + ":" + pass; + String A2 = request.getMethod().toUpperCase() + ":" + uri; + byte[] mdbytes = messageDigest.digest(A1.getBytes()); + String HA1 = toHexString(mdbytes); + + + mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + String cnonce = authHeader.getCNonce(); + String KD = HA1 + ":" + nonce; + + if (qop != null && qop.equals("auth") ) { + if (nc != -1) { + KD += ":" + ncStr; + } + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + qop; + } + KD += ":" + HA2; + mdbytes = messageDigest.digest(KD.getBytes()); + String mdString = toHexString(mdbytes); + String response = authHeader.getResponse(); + return mdString.equals(response); + + } +} + diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/RecordApiUtils.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/RecordApiUtils.java new file mode 100644 index 00000000..7e9924b8 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/RecordApiUtils.java @@ -0,0 +1,170 @@ +package com.fastbee.sip.util; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.stereotype.Component; + +import javax.validation.constraints.NotNull; +import java.io.IOException; +import java.net.ConnectException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Component +public class RecordApiUtils { + public interface RequestCallback { + void run(JSONObject response); + } + + private OkHttpClient getClient() { + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + return httpClientBuilder.build(); + } + + public JSONObject sendGet(String recoreUrl, String api, Map param, RequestCallback callback) { + OkHttpClient client = getClient(); + StringBuilder stringBuffer = new StringBuilder(); + stringBuffer.append(String.format("%s/%s", recoreUrl, api)); + JSONObject responseJSON = null; + if (param != null && param.keySet().size() > 0) { + stringBuffer.append("?"); + int index = 1; + for (String key : param.keySet()) { + if (param.get(key) != null) { + stringBuffer.append(key).append("=").append(param.get(key)); + if (index < param.size()) { + stringBuffer.append("&"); + } + } + index++; + } + } + String url = stringBuffer.toString(); + Request request = new Request.Builder() + .get() + .url(url) + .build(); + if (callback == null) { + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + String responseStr = responseBody.string(); + responseJSON = JSON.parseObject(responseStr); + } + } else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } catch (ConnectException e) { + log.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + log.error("请检查media配置并确认Assist已启动..."); + } catch (IOException e) { + log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + } else { + client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + try { + String responseStr = Objects.requireNonNull(response.body()).string(); + callback.run(JSON.parseObject(responseStr)); + } catch (IOException e) { + log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + + } else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + log.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + log.info("请检查media配置并确认Assist已启动..."); + } + }); + } + return responseJSON; + } + + public JSONObject fileDuration(String recoreUrl, String app, String stream, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("recordIng", true); + return sendGet(recoreUrl, "zlm/record//file/duration", param, callback); + } + + public JSONObject getInfo(String recoreUrl, RequestCallback callback) { + Map param = new HashMap<>(); + return sendGet(recoreUrl, "zlm/record//info", param, callback); + } + + public JSONObject getRecordlist(String recoreUrl, Integer pageNum, Integer pageSize, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("pageNum", pageNum); + param.put("pageSize", pageSize); + return sendGet(recoreUrl, "zlm/record/list", param, callback); + } + + public JSONObject getRecordDatelist(String recoreUrl, Integer year, Integer month, String app, String stream, RequestCallback callback) { + Map param = new HashMap<>(); + if(year != null) { + param.put("year", year); + } + if(year != null) { + param.put("month", month); + } + param.put("app", app); + param.put("stream", stream); + return sendGet(recoreUrl, "zlm/record/date/list", param, callback); + } + + public JSONObject getRecordStreamlist(String recoreUrl, Integer pageNum, Integer pageSize, String app, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("page", pageNum); + param.put("count", pageSize); + param.put("app", app); + return sendGet(recoreUrl, "zlm/record/stream/list", param, callback); + } + + public JSONObject getRecordApplist(String recoreUrl, Integer pageNum, Integer pageSize, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("page", pageNum); + param.put("count", pageSize); + return sendGet(recoreUrl, "zlm/record/app/list", param, callback); + } + + public JSONObject getRecordFilelist(String recoreUrl, Integer pageNum, Integer pageSize, String app, String stream, String startTime, String endTime, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("page", pageNum); + param.put("count", pageSize); + param.put("app", app); + param.put("stream", stream); + param.put("startTime", startTime); + param.put("endTime", endTime); + return sendGet(recoreUrl, "zlm/record/file/list", param, callback); + } + + public JSONObject addStreamCallInfo(String recoreUrl, String app, String stream, String callId, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("callId", callId); + return sendGet(recoreUrl, "zlm/record/addStreamCallInfo", param, callback); + } + + public JSONObject uploadOss(String recoreUrl, String file, RequestCallback callback) { + Map param = new HashMap<>(); + param.put("resourcePath", file); + return sendGet(recoreUrl, "file/upload", param, callback); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/SipUtil.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/SipUtil.java new file mode 100644 index 00000000..afdc33c6 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/SipUtil.java @@ -0,0 +1,175 @@ +package com.fastbee.sip.util; + +import gov.nist.javax.sip.address.AddressImpl; +import gov.nist.javax.sip.address.SipUri; +import org.apache.commons.lang3.RandomStringUtils; + +import javax.sip.header.FromHeader; +import javax.sip.message.Request; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class SipUtil { + private static final String date_format_T = "yyyy-MM-dd'T'HH:mm:ss"; + private static final String date_format = "yyyy-MM-dd HH:mm:ss"; + public static final Long DEFAULT_EXEC_TIMEOUT = 20000L; + private static String ssrcPrefix; + + private static List isUsed; + + private static List notUsed; + + public final static String BUSINESS_GROUP = "215"; + public final static String VIRTUAL_ORGANIZATION = "216"; + public static final String TREETYPE_BUSINESS_GROUP = "BusinessGroup"; + public static final String TREETYPE_CIVIL_CODE = "CivilCode"; + + private static void init() { + isUsed = new ArrayList(); + notUsed = new ArrayList(); + for (int i = 1; i < 10000; i++) { + if (i < 10) { + notUsed.add("000" + i); + } else if (i < 100) { + notUsed.add("00" + i); + } else if (i < 1000) { + notUsed.add("0" + i); + } else { + notUsed.add(String.valueOf(i)); + } + } + } + + /** + * 获取视频预览的SSRC值,第一位固定为0 + * + */ + public static String getPlaySsrc(String domain) { + return "0" + getSsrcPrefix(domain) + getSN(); + } + + /** + * 获取录像回放的SSRC值,第一位固定为1 + * + */ + public static String getPlayBackSsrc(String domain) { + return "1" + getSsrcPrefix(domain) + getSN(); + } + + /** + * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽 + * + */ + public static void releaseSsrc(String ssrc) { + String sn = ssrc.substring(6); + isUsed.remove(sn); + notUsed.add(sn); + } + + /** + * 获取后四位数SN,随机数 + * + */ + private static String getSN() { + String sn = null; + int index = 0; + if (notUsed.size() == 0) { + throw new RuntimeException("ssrc已经用完"); + } else if (notUsed.size() == 1) { + sn = notUsed.get(0); + } else { + index = new Random().nextInt(notUsed.size() - 1); + sn = notUsed.get(index); + } + notUsed.remove(index); + isUsed.add(sn); + return sn; + } + + private static String getSsrcPrefix(String domain) { + if (ssrcPrefix == null) { + ssrcPrefix = domain.substring(3, 8); + init(); + } + return ssrcPrefix; + } + + public static String dateToISO8601(Date date) { + SimpleDateFormat newsdf = new SimpleDateFormat(date_format_T, Locale.getDefault()); + return newsdf.format(date); + } + + public static String timestampToISO8601(String formatTime) { + Date date = new Date(Long.parseLong(formatTime) * 1000); + SimpleDateFormat newsdf = new SimpleDateFormat(date_format_T, Locale.getDefault()); + return newsdf.format(date); + } + + public static long ISO8601Totimestamp(String formatTime) { + SimpleDateFormat oldsdf = new SimpleDateFormat(date_format_T, Locale.getDefault()); + try { + return oldsdf.parse(formatTime).getTime() / 1000; + } catch (ParseException e) { + e.printStackTrace(); + } + return 0; + } + + public static Date ISO8601ToDate(String formatTime) { + SimpleDateFormat oldsdf = new SimpleDateFormat(date_format_T, Locale.getDefault()); + try { + return oldsdf.parse(formatTime); + } catch (ParseException e) { + e.printStackTrace(); + } + return null; + } + + public static long DateStringToTimestamp(String formatTime) { + SimpleDateFormat format = new SimpleDateFormat(date_format); + //设置要读取的时间字符串格式 + Date date; + try { + date = format.parse(formatTime); + //转换为Date类 + return date.getTime() / 1000; + } catch (ParseException e) { + e.printStackTrace(); + } + return 0; + } + + public static Date timestampToDate(String formatTime) { + return new Date(Long.parseLong(formatTime) * 1000); + } + + public static String safeString(Object val) { + if (val == null) { + return ""; + } + + return val.toString(); + } + public static String getUserIdFromFromHeader(Request request) { + FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); + return getUserIdFromFromHeader(fromHeader); + } + public static String getUserIdFromFromHeader(FromHeader fromHeader) { + AddressImpl address = (AddressImpl)fromHeader.getAddress(); + SipUri uri = (SipUri) address.getURI(); + return uri.getUser(); + } + + public static String getNewViaTag() { + return "z9hG4bK" + RandomStringUtils.randomNumeric(10); + } + + public static String getNewFromTag(){ + return UUID.randomUUID().toString().replace("-", ""); + } + + public static String getNewTag(){ + return String.valueOf(System.currentTimeMillis()); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/WebAsyncUtil.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/WebAsyncUtil.java new file mode 100644 index 00000000..5830bfdd --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/WebAsyncUtil.java @@ -0,0 +1,24 @@ +package com.fastbee.sip.util; + +import com.fastbee.sip.util.result.BaseResult; +import com.fastbee.sip.util.result.CodeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.context.request.async.WebAsyncTask; + +import java.util.concurrent.Callable; + +@Slf4j +public class WebAsyncUtil { + public static final Long COMMON_TIMEOUT = 30000L; + + public static WebAsyncTask init(ThreadPoolTaskExecutor executor, Callable callable) { + WebAsyncTask asyncTask = new WebAsyncTask<>(COMMON_TIMEOUT, executor, callable); + + asyncTask.onCompletion(() -> log.info("任务执行完成")); + asyncTask.onError(() -> BaseResult.out(CodeEnum.FAIL, "error")); + asyncTask.onTimeout(() -> BaseResult.out(CodeEnum.FAIL, "timeout")); + + return asyncTask; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/XmlUtil.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/XmlUtil.java new file mode 100644 index 00000000..ecb4ed65 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/XmlUtil.java @@ -0,0 +1,118 @@ +package com.fastbee.sip.util; + +import lombok.extern.slf4j.Slf4j; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; + +import javax.sip.RequestEvent; +import javax.sip.message.Request; +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.util.*; + +@Slf4j +public class XmlUtil { + + /** + * 解析XML为Document对象 + * + * @param xml + * 被解析的XMl + * @return Document + */ + public static Element parseXml(String xml) + { + Document document = null; + // + StringReader sr = new StringReader(xml); + SAXReader saxReader = new SAXReader(); + try + { + document = saxReader.read(sr); + } + catch (DocumentException e) + { + log.error("解析失败", e); + } + return null == document ? null : document.getRootElement(); + } + + /** + * 获取element对象的text的值 + * + * @param em + * 节点的对象 + * @param tag + * 节点的tag + * @return 节点 + */ + public static String getText(Element em, String tag) + { + if (null == em) + { + return null; + } + Element e = em.element(tag); + // + return null == e ? null : e.getText(); + } + + /** + * 递归解析xml节点,适用于 多节点数据 + * + * @param node + * node + * @param nodeName + * nodeName + * @return List> + */ + public static List> listNodes(Element node, String nodeName) + { + if (null == node) + { + return null; + } + // 初始化返回 + List> listMap = new ArrayList>(); + // 首先获取当前节点的所有属性节点 + List list = node.attributes(); + + Map map = null; + // 遍历属性节点 + for (Attribute attribute : list) + { + if (nodeName.equals(node.getName())) + { + if (null == map) + { + map = new HashMap(); + listMap.add(map); + } + // 取到的节点属性放到map中 + map.put(attribute.getName(), attribute.getValue()); + } + + } + // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 + // 使用递归 + Iterator iterator = node.elementIterator(); + while (iterator.hasNext()) + { + Element e = iterator.next(); + listMap.addAll(listNodes(e, nodeName)); + } + return listMap; + } + + public static Element getRootElement(RequestEvent evt) throws DocumentException { + Request request = evt.getRequest(); + SAXReader reader = new SAXReader(); + reader.setEncoding("gbk"); + Document xml = reader.read(new ByteArrayInputStream(request.getRawContent())); + return xml.getRootElement(); + } + +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java new file mode 100644 index 00000000..21c4468f --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java @@ -0,0 +1,388 @@ +package com.fastbee.sip.util; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.sip.domain.MediaServer; +import com.fastbee.sip.model.ZlmMediaServer; +import com.fastbee.sip.service.IMediaServerService; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.ConnectException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + + +@Slf4j +@Component +public class ZlmApiUtils { + @Autowired + private IMediaServerService mediaServerService; + + private final int[] udpPortRangeArray = new int[2]; + private OkHttpClient client; + private int currentPort = 0; + + private OkHttpClient getClient() { + if (client == null) { + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + // 设置连接超时时间 + httpClientBuilder.connectTimeout(5, TimeUnit.SECONDS); + // 设置读取超时时间 + httpClientBuilder.readTimeout(10, TimeUnit.SECONDS); + // 设置连接池 + httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); + client = httpClientBuilder.build(); + } + return client; + + } + + public JSONObject sendPost(MediaServer media, String api, Map param) { + JSONObject responseJSON = null; + if (media != null) { + OkHttpClient client = getClient(); + String url = String.format("http://%s:%s/index/api/%s", media.getIp(), media.getPortHttp(), api); + log.debug(url); + + FormBody.Builder builder = new FormBody.Builder(); + builder.add("secret", media.getSecret()); + if (param != null) { + for (String key : param.keySet()) { + builder.add(key, param.get(key).toString()); + } + } + + FormBody body = builder.build(); + Request request = new Request.Builder().post(body).url(url).build(); + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + assert response.body() != null; + String responseStr = response.body().string(); + responseJSON = JSON.parseObject(responseStr); + } + } catch (ConnectException e) { + log.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + log.info("请检查media配置并确认ZLM已启动..."); + } catch (IOException e) { + e.printStackTrace(); + } + + } + return responseJSON; + } + + //config + public ZlmMediaServer getMediaServerConfig(MediaServer media) { + JSONObject responseJSON = sendPost(media, "getServerConfig", null); + ZlmMediaServer mediaServer = null; + if (responseJSON != null) { + JSONArray data = responseJSON.getJSONArray("data"); + if (data != null && data.size() > 0) { + log.info("流媒体服务配置:{}}", data); + mediaServer = JSON.parseObject(JSON.toJSONString(data.get(0)), ZlmMediaServer.class); + } + } else { + log.error("获取流媒体服务配置失败!"); + } + return mediaServer; + } + + public JSONObject setServerConfig(MediaServer media, Map param) { + return sendPost(media, "setServerConfig", param); + } + + public JSONObject restartServer(MediaServer media) { + return sendPost(media, "restartServer", null); + } + + public JSONObject getMediaList(MediaServer media, String app, String schema, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("vhost", "__defaultVhost__"); + if (stream != null) { + param.put("stream", stream); + } + if (schema != null) { + param.put("schema", schema); + } + return sendPost(media, "getMediaInfo", param); + } + + //Record + public JSONObject startRecord(MediaServer media, String type, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("type", type); //0为hls,1为mp4 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + //可选 customized_path 录像保存目录 + //可选 max_second mp4录像切片时间大小,单位秒,置0则采用配置项 + return sendPost(media, "startRecord", param); + } + + public JSONObject stopRecord(MediaServer media, String type, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("type", type); //0为hls,1为mp4 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + return sendPost(media, "stopRecord", param); + } + + public JSONObject isRecording(MediaServer media, String type, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("type", type); //0为hls,1为mp4 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + return sendPost(media, "isRecording", param); + } + + public JSONObject getMp4RecordFile(MediaServer media, String period, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("period", period); //示例值:2020-05-26 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + //可选 customized_path 自定义搜索路径,与startRecord方法中的customized_path一样,默认为配置文件的路径 + return sendPost(media, "getMp4RecordFile", param); + } + + public JSONObject setRecordSpeed(MediaServer media, String speed, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("speed", speed); //要设置的录像倍速 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + return sendPost(media, "setRecordSpeed", param); + } + + public JSONObject seekRecordStamp(MediaServer media, String stamp, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stamp", stamp); //要设置的录像播放位置 + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + return sendPost(media, "seekRecordStamp", param); + } + + //Rtp + public JSONObject getRtpInfo(MediaServer media, String stream_id) { + Map param = new HashMap<>(); + param.put("stream_id", stream_id); + return sendPost(media, "getRtpInfo", param); + } + + public JSONObject openRtpServer(MediaServer media, Map param) { + return sendPost(media, "openRtpServer", param); + } + + public JSONObject closeRtpServer(MediaServer media, Map param) { + return sendPost(media, "closeRtpServer", param); + } + + public JSONObject listRtpServer(MediaServer media) { + Map param = new HashMap<>(); + return sendPost(media, "listRtpServer", null); + } + + public JSONObject startSendRtp(MediaServer media, String app, String stream, String ssrc, String dst_url, String dst_port, String is_udp) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("vhost", "__defaultVhost__"); + param.put("stream", stream); + param.put("ssrc", ssrc); //rtp推流的ssrc,ssrc不同时,可以推流到多个上级服务器 + param.put("dst_url", dst_url); // 目标ip或域名 + param.put("dst_port", dst_port); //`目标端口 + param.put("is_udp", is_udp); //是否udp方式 + // 可选参数 src_port 使用的本机端口,为0或不传时默认为随机端口 + // 可选参数 pt 发送时,rtp的pt(uint8_t),不传时默认为96 + // 可选参数 use_ps 发送时,rtp的负载类型。为1时,负载为ps;为0时,为es;不传时默认为1 + // 可选参数 only_audio 当use_ps 为0时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0 + return sendPost(media, "seekRecordStamp", param); + } + + public JSONObject startSendRtpPassive(MediaServer media, String ssrc, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + param.put("ssrc", ssrc); //rtp推流的ssrc,ssrc不同时,可以推流到多个上级服务器 + // 可选参数 src_port 使用的本机端口,为0或不传时默认为随机端口 + // 可选参数 pt 发送时,rtp的pt(uint8_t),不传时默认为96 + // 可选参数 use_ps 发送时,rtp的负载类型。为1时,负载为ps;为0时,为es;不传时默认为1 + // 可选参数 only_audio 当use_ps 为0时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0 + return sendPost(media, "seekRecordStamp", param); + } + + public JSONObject stopSendRtp(MediaServer media, String ssrc, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("vhost", "__defaultVhost__"); + param.put("ssrc", ssrc); //可选参数 根据ssrc关停某路rtp推流,置空时关闭所有流 + return sendPost(media, "seekRecordStamp", param); + } + + public int createRTPServer(MediaServer media, String streamId, String ssrc, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) { + int result = -1; + // 查询此rtp server 是否已经存在 + JSONObject rtpInfo = getRtpInfo(media, streamId); + log.info(JSONObject.toJSONString(rtpInfo)); + if (rtpInfo.getInteger("code") == 0) { + if (rtpInfo.getBoolean("exist")) { + result = rtpInfo.getInteger("local_port"); + if (result == 0) { + // 此时说明rtpServer已经创建但是流还没有推上来 + // 此时重新打开rtpServer + Map param = new HashMap<>(); + param.put("stream_id", streamId); + JSONObject jsonObject = closeRtpServer(media, param); + if (jsonObject != null) { + if (jsonObject.getInteger("code") == 0) { + return createRTPServer(media, streamId, ssrc, onlyAuto, reUsePort, tcpMode); + } else { + log.warn("[开启rtpServer], 重启RtpServer错误"); + } + } + } + return result; + } + } else if (rtpInfo.getInteger("code") == -2) { + return result; + } + + if (media != null) { + Map param = new HashMap<>(); + // 推流端口设置0则使用随机端口 + param.put("port", 0); + if (tcpMode == null) { + tcpMode = 0; + } + //0 udp 模式,1 tcp 被动模式, 2 tcp 主动模式。 (兼容enable_tcp 为0/1) + param.put("tcp_mode", tcpMode); + param.put("stream_id", streamId); + if (reUsePort != null) { + param.put("re_use_port", reUsePort ? "1" : "0"); + } + if (onlyAuto != null) { + param.put("only_audio", onlyAuto ? "1" : "0"); + } + if (ssrc != null) { + param.put("ssrc", ssrc); + } + JSONObject jsonObject = openRtpServer(media, param); + log.warn("param:{},createRTPServer:{}", param, jsonObject); + if (jsonObject != null) { + if (jsonObject.getInteger("code") == 0) { + result = jsonObject.getInteger("port"); + } else { + log.error("创建RTP Server 失败 {}: ", jsonObject.getString("msg")); + } + } else { + log.error("创建RTP Server 失败: 请检查ZLM服务"); + } + } + return result; + } + + public boolean closeRTPServer(MediaServer media, String streamId) { + boolean result = false; + Map param = new HashMap<>(); + param.put("stream_id", streamId); + JSONObject jsonObject = closeRtpServer(media, param); + if (jsonObject != null) { + if (jsonObject.getInteger("code") == 0) { + result = jsonObject.getInteger("hit") == 1; + } else { + log.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); + } + } else { + // 检查ZLM状态 + log.error("关闭RTP Server 失败: 请检查ZLM服务"); + } + return result; + } + + private int getPortFromUdpPortRange(String udpPortRange) { + if (currentPort == 0) { + String[] udpPortRangeStrArray = udpPortRange.split(","); + udpPortRangeArray[0] = Integer.parseInt(udpPortRangeStrArray[0]); + udpPortRangeArray[1] = Integer.parseInt(udpPortRangeStrArray[1]); + } + + if (currentPort == 0 || currentPort++ > udpPortRangeArray[1]) { + currentPort = udpPortRangeArray[0]; + return udpPortRangeArray[0]; + } else { + if (currentPort % 2 == 1) { + currentPort++; + } + return currentPort++; + } + } + + //获取截图 + public void getSnap(MediaServer media, String streamUrl, int timeout_sec, int expire_sec, String fileName) { + Map param = new HashMap<>(3); + param.put("url", streamUrl); + param.put("timeout_sec", timeout_sec); + param.put("expire_sec", expire_sec); + sendGetForImg(media, "getSnap", param, fileName); + } + + public void sendGetForImg(MediaServer media, String api, Map params, String fileName) { + String url = String.format("http://%s:%s/index/api/%s", media.getIp(), media.getPortHttp(), api); + HttpUrl parseUrl = HttpUrl.parse(url); + if (parseUrl == null) { + return; + } + HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); + httpBuilder.addQueryParameter("secret", media.getSecret()); + if (params != null) { + for (Map.Entry param : params.entrySet()) { + httpBuilder.addQueryParameter(param.getKey(), param.getValue().toString()); + } + } + Request request = new Request.Builder() + .url(httpBuilder.build()) + .build(); + try { + OkHttpClient client = getClient(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + File snapFolder = new File(FileUploadUtils.getDefaultBaseDir() + File.separator + "snap"); + if (!snapFolder.exists()) { + if (!snapFolder.mkdirs()) { + log.warn("{}路径创建失败", snapFolder.getAbsolutePath()); + } + } + File snapFile = new File(snapFolder.getAbsolutePath() + File.separator + fileName); + log.info("截图成功:{}", snapFile.getAbsolutePath()); + FileOutputStream outStream = new FileOutputStream(snapFile); + outStream.write(Objects.requireNonNull(response.body()).bytes()); + outStream.flush(); + outStream.close(); + Objects.requireNonNull(response.body()).close(); + } else { + log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + } + } catch (ConnectException e) { + log.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + log.info("请检查media配置并确认ZLM已启动..."); + } catch (IOException e) { + log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/BaseResult.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/BaseResult.java new file mode 100644 index 00000000..582935a1 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/BaseResult.java @@ -0,0 +1,49 @@ +package com.fastbee.sip.util.result; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseResult implements Serializable { + private static final long serialVersionUID = 1383530376576722749L; + private int code; + private String msg; + + protected BaseResult() { + } + + protected BaseResult(CodeEnum codeEnum) { + this.code = codeEnum.getCode(); + this.msg = codeEnum.getMsg(); + } + + protected BaseResult(CodeEnum codeEnum, String msg) { + this.code = codeEnum.getCode(); + this.msg = msg; + } + + public static BaseResult out(CodeEnum codeEnum) { + return new BaseResult(codeEnum); + } + + public static BaseResult out(CodeEnum codeEnum, String msg) { + return new BaseResult(codeEnum, msg); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/CodeEnum.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/CodeEnum.java new file mode 100644 index 00000000..53f47d40 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/CodeEnum.java @@ -0,0 +1,25 @@ +package com.fastbee.sip.util.result; + +public enum CodeEnum { + /** + * CodeEnum + */ + SUCCESS(200, "成功"), + FAIL(500, "失败"); + + private final int code; + private final String msg; + + CodeEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/DataResult.java b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/DataResult.java new file mode 100644 index 00000000..9c22755c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/result/DataResult.java @@ -0,0 +1,50 @@ +package com.fastbee.sip.util.result; + +import java.io.Serializable; +import java.util.Objects; + +public class DataResult extends BaseResult implements Serializable { + private static final long serialVersionUID = -633787910682534734L; + private T data; + + private DataResult() { + + } + + private DataResult(CodeEnum codeEnum, T data) { + super(codeEnum); + this.data = data; + } + + public static DataResult out(CodeEnum codeEnum, T data) { + return new DataResult<>(codeEnum, data); + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + DataResult> that = (DataResult>) o; + return Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), data); + } +} diff --git a/springboot/fastbee-server/sip-server/src/main/resources/mapper/MediaServerMapper.xml b/springboot/fastbee-server/sip-server/src/main/resources/mapper/MediaServerMapper.xml new file mode 100644 index 00000000..b47ce31a --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/resources/mapper/MediaServerMapper.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, server_id, tenant_id, tenant_name, enabled, protocol, ip, domain, hookurl, secret, port_http, port_https, port_rtmp, port_rtsp, rtp_proxy_port, rtp_enable, rtp_port_range, record_port, auto_config, status, del_flag, create_by, create_time, update_by, update_time, remark from media_server + + + + + + and server_id = #{serverId} + and tenant_id = #{tenantId} + and tenant_name like concat('%', #{tenantName}, '%') + and enabled = #{enabled} + and protocol = #{protocol} + and ip = #{ip} + and domain = #{domain} + and hookurl = #{hookurl} + and secret = #{secret} + and port_http = #{portHttp} + and port_https = #{portHttps} + and port_rtmp = #{portRtmp} + and port_rtsp = #{portRtsp} + and rtp_proxy_port = #{rtpProxyPort} + and rtp_enable = #{rtpEnable} + and rtp_port_range = #{rtpPortRange} + and record_port = #{recordPort} + and auto_config = #{autoConfig} + and status = #{status} + + + + + + where id = #{id} + + + + insert into media_server + + server_id, + tenant_id, + tenant_name, + enabled, + protocol, + ip, + domain, + hookurl, + secret, + port_http, + port_https, + port_rtmp, + port_rtsp, + rtp_proxy_port, + rtp_enable, + rtp_port_range, + record_port, + auto_config, + status, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{serverId}, + #{tenantId}, + #{tenantName}, + #{enabled}, + #{protocol}, + #{ip}, + #{domain}, + #{hookurl}, + #{secret}, + #{portHttp}, + #{portHttps}, + #{portRtmp}, + #{portRtsp}, + #{rtpProxyPort}, + #{rtpEnable}, + #{rtpPortRange}, + #{recordPort}, + #{autoConfig}, + #{status}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update media_server + + server_id = #{serverId}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + enabled = #{enabled}, + protocol = #{protocol}, + ip = #{ip}, + domain = #{domain}, + hookurl = #{hookurl}, + secret = #{secret}, + port_http = #{portHttp}, + port_https = #{portHttps}, + port_rtmp = #{portRtmp}, + port_rtsp = #{portRtsp}, + rtp_proxy_port = #{rtpProxyPort}, + rtp_enable = #{rtpEnable}, + rtp_port_range = #{rtpPortRange}, + record_port = #{recordPort}, + auto_config = #{autoConfig}, + status = #{status}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from media_server where id = #{id} + + + + delete from media_server where id in + + #{id} + + + + + + + and tenant_id = #{tenantId} + + + + + + where tenant_id = #{tenantId} + + diff --git a/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipConfigMapper.xml b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipConfigMapper.xml new file mode 100644 index 00000000..c3eed7ed --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipConfigMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select id, + product_id, + product_name, + enabled, + isdefault, + seniorSdp, + domain, + server_sipid, + password, + ip, + port, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark + from sip_config + + + + + + and product_id = #{productId} + and product_name like concat('%', #{productName}, + '%') + + and enabled = #{enabled} + and isdefault = #{isdefault} + and seniorSdp = #{seniorsdp} + and domain = #{domain} + and server_sipid = #{serverSipid} + and password = #{password} + and ip = #{ip} + and port = #{port} + + + + + + where product_id = #{productId} + + + + + where product_id = #{productId} + + + insert into sip_config + + product_id, + product_name, + enabled, + isdefault, + seniorSdp, + domain, + server_sipid, + password, + ip, + port, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{productId}, + #{productName}, + #{enabled}, + #{isdefault}, + #{seniorsdp}, + #{domain}, + #{serverSipid}, + #{password}, + #{ip}, + #{port}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update sip_config + + product_id = #{productId}, + product_name = #{productName}, + enabled = #{enabled}, + isdefault = #{isdefault}, + seniorSdp = #{seniorsdp}, + domain = #{domain}, + server_sipid = #{serverSipid}, + password = #{password}, + ip = #{ip}, + port = #{port}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update sip_config + set isdefault = 0 + where isdefault = 1 + + + + delete + from sip_config + where id = #{id} + + + + delete from sip_config where id in + + #{id} + + + + + delete from sip_config where product_id in + + #{productId} + + + + diff --git a/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceChannelMapper.xml b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceChannelMapper.xml new file mode 100644 index 00000000..47fca366 --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceChannelMapper.xml @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, + tenant_id, + tenant_name, + product_id, + product_name, + user_id, + user_name, + device_sip_id, + channel_sip_id, + channel_name, + register_time, + device_type, + channel_type, + cityCode, + civilCode, + manufacture, + model, + owner, + block, + address, + parentId, + ipAddress, + port, + password, + PTZType, + PTZTypeText, + status, + longitude, + latitude, + streamId, + subCount, + parental, + hasAudio, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark + from sip_device_channel + + + + + + and tenant_id = #{tenantId} + and tenant_name like concat('%', #{tenantName}, '%') + and product_id = #{productId} + and product_name like concat('%', #{productName},'%') + and user_id = #{userId} + and user_name like concat('%', #{userName}, '%') + and device_sip_id = #{deviceSipId} + and channel_sip_id = #{channelSipId} + and channel_name like concat('%', #{channelName},'%') + and register_time = #{registerTime} + and device_type = #{deviceType} + and channel_type = #{channelType} + and cityCode = #{citycode} + and civilCode = #{civilcode} + and manufacture = #{manufacture} + and model = #{model} + and owner = #{owner} + and block = #{block} + and address = #{address} + and parentId = #{parentid} + and ipAddress = #{ipaddress} + and port = #{port} + and password = #{password} + and PTZType = #{ptztype} + and PTZTypeText = #{ptztypetext} + and status = #{status} + and longitude = #{longitude} + and latitude = #{latitude} + and streamId = #{streamid} + and subCount = #{subcount} + and parental = #{parental} + and hasAudio = #{hasaudio} + + + + + + where id = #{id} + + + + insert into sip_device_channel + + tenant_id, + tenant_name, + product_id, + product_name, + user_id, + user_name, + device_sip_id, + channel_sip_id, + channel_name, + register_time, + device_type, + channel_type, + cityCode, + civilCode, + manufacture, + model, + owner, + block, + address, + parentId, + ipAddress, + port, + password, + PTZType, + PTZTypeText, + status, + longitude, + latitude, + streamId, + subCount, + parental, + hasAudio, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{tenantId}, + #{tenantName}, + #{productId}, + #{productName}, + #{userId}, + #{userName}, + #{deviceSipId}, + #{channelSipId}, + #{channelName}, + #{registerTime}, + #{deviceType}, + #{channelType}, + #{citycode}, + #{civilcode}, + #{manufacture}, + #{model}, + #{owner}, + #{block}, + #{address}, + #{parentid}, + #{ipaddress}, + #{port}, + #{password}, + #{ptztype}, + #{ptztypetext}, + #{status}, + #{longitude}, + #{latitude}, + #{streamid}, + #{subcount}, + #{parental}, + #{hasaudio}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update sip_device_channel + + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + product_id = #{productId}, + product_name = #{productName}, + user_id = #{userId}, + user_name = #{userName}, + device_sip_id = #{deviceSipId}, + channel_sip_id = #{channelSipId}, + channel_name = #{channelName}, + register_time = #{registerTime}, + device_type = #{deviceType}, + channel_type = #{channelType}, + cityCode = #{citycode}, + civilCode = #{civilcode}, + manufacture = #{manufacture}, + model = #{model}, + owner = #{owner}, + block = #{block}, + address = #{address}, + parentId = #{parentid}, + ipAddress = #{ipaddress}, + port = #{port}, + password = #{password}, + PTZType = #{ptztype}, + PTZTypeText = #{ptztypetext}, + status = #{status}, + longitude = #{longitude}, + latitude = #{latitude}, + streamId = #{streamid}, + subCount = #{subcount}, + parental = #{parental}, + hasAudio = #{hasaudio}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where channel_sip_id = #{channelSipId} + + + + delete + from sip_device_channel + where id = #{id} + + + + delete from sip_device_channel where id in + + #{id} + + + + + + where device_sip_id = #{deviceSipId} + + + + + where channel_sip_id = #{channelSipId} + + + + + where device_sip_id = #{deviceSipId} + and parentId = #{parentid} or left(channel_sip_id, LENGTH(#{parentId})) = #{parentId} and length(channel_sip_id)=#{length} + and parentId = #{parentid} or length(channel_sip_id)=#{length} + and parentId = #{parentid} + and parentId = #{parentid} or left(channel_sip_id, LENGTH(#{parentId})) = #{parentId} + + + + + where device_sip_id = #{deviceSipId} and length(channel_sip_id)>14 and civilCode=#{parentId} + + + + + where device_sip_id=#{deviceSipId} and civilCode not in (select civilCode from sip_device_channel where device_sip_id=#{deviceSipId} group by civilCode) + + + + select min(length(channel_sip_id)) as minLength + from sip_device_channel + where device_sip_id = #{deviceSipId} + + + + update sip_device_channel + + streamId = #{streamid}, + + where id = #{id} + + + + delete + from sip_device_channel + where device_sip_id = #{deviceSipId} + + + diff --git a/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceMapper.xml b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceMapper.xml new file mode 100644 index 00000000..73237e0c --- /dev/null +++ b/springboot/fastbee-server/sip-server/src/main/resources/mapper/SipDeviceMapper.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select device_id, + product_id, + product_name, + device_sip_id, + device_name, + manufacturer, + model, + firmware, + transport, + streamMode, + online, + registerTime, + lastConnectTime, + active_time, + ip, + port, + hostAddress, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark + from sip_device + + + + + + and product_id = #{productId} + and product_name like concat('%', #{productName}, + '%') + + and device_sip_id = #{deviceSipId} + and device_name like concat('%', #{deviceName}, '%') + + and manufacturer = #{manufacturer} + and model = #{model} + and firmware = #{firmware} + and transport = #{transport} + and streamMode = #{streammode} + and online = #{online} + and registerTime = #{registertime} + and lastConnectTime = #{lastconnecttime} + and active_time = #{activeTime} + and ip = #{ip} + and port = #{port} + and hostAddress = #{hostaddress} + + + + + + where device_id = #{deviceId} + + + + insert into sip_device + + product_id, + product_name, + device_sip_id, + device_name, + manufacturer, + model, + firmware, + transport, + streamMode, + online, + registerTime, + lastConnectTime, + active_time, + ip, + port, + hostAddress, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{productId}, + #{productName}, + #{deviceSipId}, + #{deviceName}, + #{manufacturer}, + #{model}, + #{firmware}, + #{transport}, + #{streammode}, + #{online}, + #{registertime}, + #{lastconnecttime}, + #{activeTime}, + #{ip}, + #{port}, + #{hostaddress}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update sip_device + + product_id = #{productId}, + product_name = #{productName}, + device_sip_id = #{deviceSipId}, + device_name = #{deviceName}, + manufacturer = #{manufacturer}, + model = #{model}, + firmware = #{firmware}, + transport = #{transport}, + streamMode = #{streammode}, + online = #{online}, + registerTime = #{registertime}, + lastConnectTime = #{lastconnecttime}, + active_time = #{activeTime}, + ip = #{ip}, + port = #{port}, + hostAddress = #{hostaddress}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where device_id = #{deviceId} + + + + delete + from sip_device + where device_id = #{deviceId} + + + + delete from sip_device where device_id in + + #{deviceId} + + + + + update sip_device + + online = #{online}, + lastConnectTime = #{lastconnecttime}, + active_time = #{activeTime}, + ip = #{ip}, + port = #{port}, + hostaddress = #{hostaddress}, + + where device_sip_id = #{deviceSipId} + + + + + where NOW() > DATE_ADD(lastconnecttime, INTERVAL #{timeout} SECOND ) + + + + + where device_sip_id = #{deviceSipId} + + + + delete + from sip_device + where device_sip_id = #{deviceSipId} + + diff --git a/springboot/sql/fastbee2.1.sql b/springboot/sql/fastbee2.1.sql index f9398139..d033cfcf 100644 --- a/springboot/sql/fastbee2.1.sql +++ b/springboot/sql/fastbee2.1.sql @@ -1070,6 +1070,161 @@ INSERT INTO `iot_things_model_template` VALUES (341, '视频', 1, 'admin', 'vide INSERT INTO `iot_things_model_template` VALUES (342, '图片', 1, 'admin', 'image', 1, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 23:21:48', '', '2023-08-30 23:25:22', NULL, '2#2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); INSERT INTO `iot_things_model_template` VALUES (343, '状态', 1, 'admin', 'status', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 23:28:00', '', '2023-08-30 23:28:17', NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +DROP TABLE IF EXISTS `media_server`; +CREATE TABLE `media_server` ( + `id` bigint(64) NOT NULL AUTO_INCREMENT COMMENT '流媒体配置ID', + `server_id` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '服务器标识', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `enabled` tinyint(1) NULL DEFAULT NULL COMMENT '使能开关', + `protocol` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '默认播放协议', + `ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '服务器ip', + `domain` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '服务器域名', + `hookurl` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '回调服务器地址', + `secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '流媒体密钥', + `port_http` int(11) NOT NULL DEFAULT 0 COMMENT 'http端口', + `port_https` int(11) NOT NULL DEFAULT 0 COMMENT 'https端口', + `port_rtmp` int(11) NOT NULL DEFAULT 0 COMMENT 'rtmp端口', + `port_rtsp` int(11) NOT NULL DEFAULT 0 COMMENT 'rtsp端口', + `rtp_proxy_port` int(11) NOT NULL DEFAULT 0 COMMENT 'RTP收流端口', + `rtp_enable` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否使用多端口模式', + `rtp_port_range` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'rtp端口范围', + `record_port` int(11) NOT NULL DEFAULT 0 COMMENT '录像服务端口', + `auto_config` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否自动同步配置ZLM', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `port_ws` int(11) NULL DEFAULT NULL COMMENT 'ws端口', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '流媒体服务器配置' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of media_server +-- ---------------------------- +INSERT INTO `media_server` VALUES (7, 'fastbee', 1, 'admin', 1, 'http', '192.168.2.120', 'fastbee.com2', '192.168.2.15:8080', '035c73f7-bb6b-4889-a715-d9eb2d192xxx', 8082, 8443, 1935, 554, 0, 1, '30000,30103', 18081, 1, 0, '0', '', '2023-09-26 21:11:43', '', '2023-10-26 21:51:25', NULL, NULL); + +DROP TABLE IF EXISTS `sip_device_channel`; +CREATE TABLE `sip_device_channel` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `product_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品名称', + `user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '产品ID', + `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品名称', + `device_sip_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '设备SipID', + `channel_sip_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '通道SipID', + `channel_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '通道名称', + `register_time` datetime NULL DEFAULT NULL COMMENT '注册时间', + `device_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '设备类型', + `channel_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '通道类型', + `cityCode` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '城市编码', + `civilCode` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '行政区域', + `manufacture` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '厂商名称', + `model` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品型号', + `owner` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '设备归属', + `block` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '警区', + `address` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '安装地址', + `parentId` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '父级id', + `ipAddress` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '设备入网IP', + `port` bigint(10) NULL DEFAULT 0 COMMENT '设备接入端口号', + `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '密码', + `PTZType` bigint(20) NOT NULL DEFAULT 0 COMMENT 'PTZ类型', + `PTZTypeText` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'PTZ类型描述字符串', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '设备状态(1-未激活,2-禁用,3-在线,4-离线)', + `longitude` double(11, 6) NULL DEFAULT NULL COMMENT '设备经度', + `latitude` double(11, 6) NULL DEFAULT NULL COMMENT '设备纬度', + `streamId` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '流媒体ID', + `subCount` bigint(20) NOT NULL DEFAULT 0 COMMENT '子设备数', + `parental` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否有子设备(1-有, 0-没有)', + `hasAudio` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否含有音频(1-有, 0-没有)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`, `device_sip_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '监控设备通道信息' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sip_device_channel +-- ---------------------------- +INSERT INTO `sip_device_channel` VALUES (84, 1, 'admin', 118, '监控设备', 1, 'admin', '11010200001320000001', '11010200001320000001', 'IPdome', '2024-01-08 22:16:32', '132', '132', '北京市/市辖区/西城区', '3402000000', 'Hikvision', 'IP Camera', 'Owner', '', 'Address', '34020000002000000001', '', 0, '', 0, '', 3, 0.000000, 0.000000, 'gb_play_11010200001320000001_11010200001320000001', 0, 0, 0, '0', '', '2023-04-11 21:12:33', '', NULL, NULL); +INSERT INTO `sip_device_channel` VALUES (102, 1, 'admin', 135, '视频监控', 0, '', '11010100001320000001', '11010100001320000001', 'IPdome', '2024-01-09 23:35:00', '132', '132', '北京市/市辖区/东城区', '3402000000', 'Hikvision', 'IP Camera', 'Owner', '', 'Address', '34020000002000000001', '', 0, '', 0, '', 3, 0.000000, 0.000000, '', 0, 0, 0, '0', '', '2024-01-08 22:15:57', '', NULL, NULL); + +DROP TABLE IF EXISTS `sip_device`; +CREATE TABLE `sip_device` ( + `device_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设备ID', + `product_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品名称', + `device_sip_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '设备SipID', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '设备名称', + `manufacturer` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '厂商名称', + `model` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品型号', + `firmware` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '固件版本', + `transport` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'UDP' COMMENT '传输模式', + `streamMode` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'UDP' COMMENT '流模式', + `online` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '在线状态', + `registerTime` datetime NOT NULL COMMENT '注册时间', + `lastConnectTime` datetime NULL DEFAULT NULL COMMENT '最后上线时间', + `active_time` datetime NULL DEFAULT NULL COMMENT '激活时间', + `ip` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备入网IP', + `port` bigint(10) NULL DEFAULT NULL COMMENT '设备接入端口号', + `hostAddress` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备地址', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`device_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '监控设备' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sip_device +-- ---------------------------- +INSERT INTO `sip_device` VALUES (9, 0, '', '13030300001170000008', '8E085C3RAJE156F', 'Dahua', 'DH-3H3205-ADW', '2.810.0000027.0.R,2022-08-26', 'UDP', 'UDP', '', '2023-02-27 12:07:35', '2023-02-26 23:36:45', NULL, '177.7.0.1', 35332, '177.7.0.1:35332', '0', '', NULL, '', NULL, NULL); +INSERT INTO `sip_device` VALUES (12, 0, '', '11010100001320000001', '海康威视摄像头', 'Hikvision', 'iDS-2DE2402IX-D3/W/XM', 'V5.7.4', 'UDP', 'UDP', '', '2024-01-09 23:29:52', '2024-01-09 23:35:00', NULL, '192.168.2.119', 5065, '192.168.2.119:5065', '0', '', NULL, '', NULL, NULL); +INSERT INTO `sip_device` VALUES (13, 0, '', '11010200001320000017', '', '', '', '', 'UDP', 'UDP', '', '2023-03-16 21:41:45', '2023-03-16 21:52:50', NULL, '192.168.2.119', 5060, '192.168.2.119:5060', '0', '', NULL, '', NULL, NULL); +INSERT INTO `sip_device` VALUES (16, 0, '', '12010100001320000003', 'IP DOME', 'Hikvision', 'iDS-2DE2402IX-D3/W/XM', 'V5.7.4', 'UDP', 'UDP', '', '2023-04-11 21:08:07', '2023-04-11 21:13:16', NULL, '192.168.2.119', 5060, '192.168.2.119:5060', '0', '', NULL, '', NULL, NULL); +INSERT INTO `sip_device` VALUES (18, 0, '', '13030100001320000001', '', 'ABCD', 'TEST001', 'V1.0', 'UDP', 'UDP', '', '2023-03-28 16:06:45', '2023-03-28 16:09:52', NULL, '192.168.205.250', 5063, '192.168.205.250:5063', '0', '', NULL, '', NULL, NULL); +INSERT INTO `sip_device` VALUES (19, 0, '', '11010200001320000001', '海康威视摄像头', 'Hikvision', 'iDS-2DE2402IX-D3/W/XM', 'V5.7.4', 'UDP', 'UDP', '', '2024-01-08 22:08:27', '2024-01-08 22:16:32', NULL, '192.168.2.119', 5065, '192.168.2.119:5065', '0', '', NULL, '', NULL, NULL); + +DROP TABLE IF EXISTS `sip_config`; +CREATE TABLE `sip_config` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `product_id` bigint(20) NOT NULL COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '产品名称', + `enabled` tinyint(1) NULL DEFAULT NULL COMMENT '使能开关', + `isdefault` tinyint(1) NULL DEFAULT NULL COMMENT '系统默认配置', + `seniorSdp` tinyint(1) NULL DEFAULT NULL COMMENT '拓展sdp', + `domain` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '服务器域', + `server_sipid` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '服务器sipid', + `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'sip认证密码', + `ip` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'sip接入IP', + `port` bigint(10) NULL DEFAULT NULL COMMENT 'sip接入端口号', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'sip系统配置' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sip_config +-- ---------------------------- +INSERT INTO `sip_config` VALUES (38, 117, '', 1, 1, NULL, '3402000000', '34020000002000000001', '12345678', '177.7.0.13', 5061, '0', '', '2023-03-16 21:26:18', '', '2023-03-16 21:26:24', NULL); +INSERT INTO `sip_config` VALUES (39, 118, '', 1, 1, NULL, '3402000000', '34020000002000000001', '12345678', '177.7.0.13', 5061, '0', '', '2023-04-11 21:11:54', '', NULL, NULL); +INSERT INTO `sip_config` VALUES (41, 135, '', 1, 1, NULL, '3402000000', '34020000002000000001', '12345678', '177.7.0.13', 5061, '0', '', '2024-01-08 22:14:35', '', NULL, NULL); + -- ---------------------------- -- Table structure for news -- ---------------------------- diff --git a/vue/src/api/iot/channel.js b/vue/src/api/iot/channel.js new file mode 100644 index 00000000..84441428 --- /dev/null +++ b/vue/src/api/iot/channel.js @@ -0,0 +1,105 @@ +import request from '@/utils/request' + +// 查询监控设备通道信息列表 +export function listChannel(query) { + return request({ + url: '/sip/channel/list', + method: 'get', + params: query + }) +} + +// 查询监控设备通道信息详细 +export function getChannel(channelId) { + return request({ + url: '/sip/channel/' + channelId, + method: 'get' + }) +} + +// 新增监控设备通道信息 +export function addChannel(createNum, data) { + return request({ + url: '/sip/channel/' + createNum, + method: 'post', + data: data + }) +} + +// 修改监控设备通道信息 +export function updateChannel(data) { + return request({ + url: '/sip/channel', + method: 'put', + data: data + }) +} + +// 删除监控设备通道信息 +export function delChannel(channelId) { + return request({ + url: '/sip/channel/' + channelId, + method: 'delete' + }) +} + +// 开始播放 +export function startPlay(deviceId, channelId) { + return request({ + url: '/sip/player/play/' + deviceId + "/" + channelId, + method: 'get' + }) +} + +// 获取流信息 +export function getStreaminfo(deviceId, channelId) { + return request({ + url: '/sip/player/playstream/' + deviceId + "/" + channelId, + method: 'get' + }) +} + +export function playback(deviceId, channelId, query) { + return request({ + url: '/sip/player/playback/' + deviceId + "/" + channelId, + method: 'get', + params: query + }) +} + +export function closeStream(deviceId, channelId, streamId){ + return request({ + url: '/sip/player/closeStream/' + deviceId + "/" + channelId + "/" + streamId, + method: 'get' + }) +} + +export function playbackPause(deviceId, channelId, streamId) { + return request({ + url: '/sip/player/playbackPause/' + deviceId + "/" + channelId + "/" + streamId, + method: 'get' + }) +} + +export function playbackReplay(deviceId, channelId, streamId) { + return request({ + url: '/sip/player/playbackReplay/' + deviceId + "/" + channelId + "/" + streamId, + method: 'get' + }) +} + +export function playbackSeek(deviceId, channelId, streamId, query) { + return request({ + url: '/sip/player/playbackSeek/' + deviceId + "/" + channelId + "/" + streamId, + method: 'get', + params: query + }) +} + +export function playbackSpeed(deviceId, channelId, streamId, query) { + return request({ + url: '/sip/player/playbackSpeed/' + deviceId + "/" + channelId + "/" + streamId, + method: 'get', + params: query + }) +} diff --git a/vue/src/api/iot/mediaServer.js b/vue/src/api/iot/mediaServer.js new file mode 100644 index 00000000..0632edd9 --- /dev/null +++ b/vue/src/api/iot/mediaServer.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询流媒体服务器配置列表 +export function listmediaServer(query) { + return request({ + url: '/sip/mediaserver/list', + method: 'get', + params: query + }) +} + +// 查询流媒体服务器配置详细 +export function getmediaServer() { + return request({ + url: '/sip/mediaserver/', + method: 'get' + }) +} + +// 新增流媒体服务器配置 +export function addmediaServer(data) { + return request({ + url: '/sip/mediaserver', + method: 'post', + data: data + }) +} + +// 修改流媒体服务器配置 +export function updatemediaServer(data) { + return request({ + url: '/sip/mediaserver', + method: 'put', + data: data + }) +} + +// 删除流媒体服务器配置 +export function delmediaServer(id) { + return request({ + url: '/sip/mediaserver/' + id, + method: 'delete' + }) +} + +export function checkmediaServer(query) { + return request({ + url: '/sip/mediaserver/check' , + method: 'get', + params: query + }) +} diff --git a/vue/src/api/iot/record.js b/vue/src/api/iot/record.js new file mode 100644 index 00000000..2391ab6e --- /dev/null +++ b/vue/src/api/iot/record.js @@ -0,0 +1,88 @@ +import request from '@/utils/request' + +export function getDevRecord(deviceId,channelId,query) { + return request({ + url: '/sip/record/devquery/' + deviceId + "/" + channelId, + method: 'get', + params: query + }) +} + +export function getRecord(channelId,sn) { + return request({ + url: '/sip/record/query/' + channelId + "/" + sn, + method: 'get', + }) +} + +export function getServerRecord(query) { + return request({ + url: '/sip/record/serverRecord/list', + method: 'get', + params: query + }) +} + +export function getServerRecordByDate(query) { + return request({ + url: '/sip/record/serverRecord/date/list', + method: 'get', + params: query + }) +} + +export function getServerRecordByStream(query) { + return request({ + url: '/sip/record/serverRecord/stream/list', + method: 'get', + params: query + }) +} + +export function getServerRecordByApp(query) { + return request({ + url: '/sip/record/serverRecord/app/list', + method: 'get', + params: query + }) +} + +export function getServerRecordByFile(query) { + return request({ + url: '/sip/record/serverRecord/file/list', + method: 'get', + params: query + }) +} + +export function getServerRecordByDevice(query) { + return request({ + url: '/sip/record/serverRecord/device/list', + method: 'get', + params: query + }) +} + +export function startPlayRecord(deviceId, channelId) { + return request({ + url: '/sip/record/play/' + deviceId + "/" + channelId, + method: 'get' + }) +} + +export function startDownloadRecord(deviceId, channelId, query) { + return request({ + url: '/sip/record/download/' + deviceId + "/" + channelId, + method: 'get', + params: query + }) +} + + +export function uploadRecord(query) { + return request({ + url: '/sip/record/upload', + method: 'get', + params: query + }) +} diff --git a/vue/src/api/iot/sipConfig.js b/vue/src/api/iot/sipConfig.js new file mode 100644 index 00000000..418d7ddb --- /dev/null +++ b/vue/src/api/iot/sipConfig.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// 查询sip系统配置详细 +export function getSipconfig(productId,isDefault) { + return request({ + url: '/sip/sipconfig/' + productId+'/'+isDefault, + method: 'get' + }) +} + +// 新增sip系统配置 +export function addSipconfig(data) { + return request({ + url: '/sip/sipconfig', + method: 'post', + data: data + }) +} + +// 修改sip系统配置 +export function updateSipconfig(data) { + return request({ + url: '/sip/sipconfig', + method: 'put', + data: data + }) +} + +export function delSipconfigByProductId(productId) { + return request({ + url: '/sip/sipconfig/product/' + productId, + method: 'delete' + }) +} diff --git a/vue/src/api/iot/sipdevice.js b/vue/src/api/iot/sipdevice.js new file mode 100644 index 00000000..fd95ef61 --- /dev/null +++ b/vue/src/api/iot/sipdevice.js @@ -0,0 +1,74 @@ +import request from '@/utils/request' + +// 查询监控设备列表 +export function listSipDevice(query) { + return request({ + url: '/sip/device/list', + method: 'get', + params: query + }) +} + +export function listSipDeviceChannel(deviceId) { + return request({ + url: '/sip/device/listchannel/'+ deviceId, + method: 'get' + }) +} + +// 查询监控设备详细 +export function getSipDevice(deviceId) { + return request({ + url: '/sip/device/' + deviceId, + method: 'get' + }) +} + +// 新增监控设备 +export function addSipDevice(data) { + return request({ + url: '/sip/device', + method: 'post', + data: data + }) +} + +// 修改监控设备 +export function updateSipDevice(data) { + return request({ + url: '/sip/device', + method: 'put', + data: data + }) +} + +// 删除监控设备 +export function delSipDevice(deviceId) { + return request({ + url: '/sip/device/' + deviceId, + method: 'delete' + }) +} + +export function delSipDeviceBySipId(sipId) { + return request({ + url: '/sip/device/sipid/' + sipId, + method: 'delete' + }) +} + +export function ptzdirection(deviceId,channelId,data) { + return request({ + url: '/sip/ptz/direction/'+ deviceId + "/" + channelId, + method: 'post', + data: data + }) +} + +export function ptzscale(deviceId,channelId,data) { + return request({ + url: '/sip/ptz/scale/'+ deviceId + "/" + channelId, + method: 'post', + data: data + }) +} diff --git a/vue/src/views/components/player/DeviceTree.vue b/vue/src/views/components/player/DeviceTree.vue new file mode 100644 index 00000000..ef586f01 --- /dev/null +++ b/vue/src/views/components/player/DeviceTree.vue @@ -0,0 +1,183 @@ + + + 设备列表 + + + + + + + + + + + + + {{ node.label }} + {{ node.label }} + + + + + + + + diff --git a/vue/src/views/components/player/deviceLiveStream.vue b/vue/src/views/components/player/deviceLiveStream.vue new file mode 100644 index 00000000..699b3d14 --- /dev/null +++ b/vue/src/views/components/player/deviceLiveStream.vue @@ -0,0 +1,203 @@ + + + + 通道名称: + + + + 开启拉流: + + 开启直播录像: + + + + + + diff --git a/vue/src/views/components/player/deviceVideo.vue b/vue/src/views/components/player/deviceVideo.vue new file mode 100644 index 00000000..4faa507b --- /dev/null +++ b/vue/src/views/components/player/deviceVideo.vue @@ -0,0 +1,291 @@ + + + + + 通道: + + + + 日期: + + + + + 查看 + + + + 时间: + + + + + + + 转存 + + + + + + + + diff --git a/vue/src/views/components/player/easyplayer.vue b/vue/src/views/components/player/easyplayer.vue new file mode 100644 index 00000000..81dca185 --- /dev/null +++ b/vue/src/views/components/player/easyplayer.vue @@ -0,0 +1,62 @@ + + + + + + + diff --git a/vue/src/views/components/player/jessibuca.vue b/vue/src/views/components/player/jessibuca.vue new file mode 100644 index 00000000..cf909326 --- /dev/null +++ b/vue/src/views/components/player/jessibuca.vue @@ -0,0 +1,363 @@ + + + + + + + + + + + + {{ kBps }} kb/s + + + + + + + + + + + + diff --git a/vue/src/views/components/player/player.vue b/vue/src/views/components/player/player.vue new file mode 100644 index 00000000..2d01ab63 --- /dev/null +++ b/vue/src/views/components/player/player.vue @@ -0,0 +1,347 @@ + + + + + + + + + + + diff --git a/vue/src/views/components/player/public/jessibuca-pro-multi.js b/vue/src/views/components/player/public/jessibuca-pro-multi.js new file mode 100644 index 00000000..8003464f --- /dev/null +++ b/vue/src/views/components/player/public/jessibuca-pro-multi.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("crypto")):"function"==typeof define&&define.amd?define(["crypto"],t):(e="undefined"!=typeof globalThis?globalThis:e||self)["jessibuca-pro-multi"]=t(e.crypto$1)}(this,(function(t){"use strict";function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=s(t);class a{on(e,t,i){const s=this.e||(this.e={});return(s[e]||(s[e]=[])).push({fn:t,ctx:i}),this}once(e,t,i){const s=this;function r(){s.off(e,r);for(var a=arguments.length,o=new Array(a),n=0;n1?i-1:0),r=1;r{delete i[e]})),void delete this.e;const s=i[e],r=[];if(s&&t)for(let e=0,i=s.length;e1?i-1:0),r=1;r1?i-1:0),r=1;r1?s-1:0),a=1;a32&&console.error("ExpGolomb: readBits() bits exceeded max 32bits!"),e<=this._current_word_bits_left){let t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}let t=this._current_word_bits_left?this._current_word:0;t>>>=32-this._current_word_bits_left;let i=e-this._current_word_bits_left;this._fillCurrentWord();let s=Math.min(i,this._current_word_bits_left),r=this._current_word>>>32-s;return this._current_word<<=s,this._current_word_bits_left-=s,t=t<>>e))return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()}readUEG(){let e=this._skipLeadingZero();return this.readBits(e+1)-1}readSEG(){let e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)}}const zr=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350,-1,-1,-1],$r=zr,Hr=zr;function Vr(e){let{profile:t,sampleRate:i,channel:s}=e;return new Uint8Array([175,0,t<<3|(14&i)>>1,(1&i)<<7|s<<3])}function Wr(e){return Jr(e)&&e[1]===vs}function Jr(e){return e[0]>>4===Et}function qr(e,t){if("mp4a.40.2"===e){if(1===t)return new Uint8Array([0,200,0,128,35,128]);if(2===t)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===t)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}}function Kr(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:9e4;return 1024*t/e}const Yr=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];class Qr{constructor(e){this.buffer=e,this.buflen=e.length,this.bufpos=0,this.bufoff=0,this.iserro=!1}read(e){let t=0,i=0;for(;e;){if(e<0||this.bufpos>=this.buflen)return this.iserro=!0,0;this.iserro=!1,i=this.bufoff+e>8?8-this.bufoff:e,t<<=i,t+=this.buffer[this.bufpos]>>8-this.bufoff-i&255>>8-i,this.bufoff+=i,e-=i,8==this.bufoff&&(this.bufpos++,this.bufoff=0)}return t}look(e){let t=this.bufpos,i=this.bufoff,s=this.read(e);return this.bufpos=t,this.bufoff=i,s}read_golomb(){let e;for(e=0;0==this.read(1)&&!this.iserro;e++);return(1<=i.byteLength)return this.eof_flag_=!0,i.byteLength;if(4095===(i[t+0]<<8|i[t+1])>>>4)return t;t++}}readNextAACFrame(){let e=this.data_,t=null;for(;null==t&&!this.eof_flag_;){let i=this.current_syncword_offset_,s=(8&e[i+1])>>>3,r=(6&e[i+1])>>>1,a=1&e[i+1],o=(192&e[i+2])>>>6,n=(60&e[i+2])>>>2,l=(1&e[i+2])<<2|(192&e[i+3])>>>6,d=(3&e[i+3])<<11|e[i+4]<<3|(224&e[i+5])>>>5;if(e[i+6],i+d>this.data_.byteLength){this.eof_flag_=!0,this.has_last_incomplete_data=!0;break}let h=1===a?7:9,c=d-h;i+=h;let u=this.findNextSyncwordOffset(i+c);if(this.current_syncword_offset_=u,0!==s&&1!==s||0!==r)continue;let p=e.subarray(i,i+c);t={},t.audio_object_type=o+1,t.sampling_freq_index=n,t.sampling_frequency=$r[n],t.channel_config=l,t.data=p}return t}hasIncompleteData(){return this.has_last_incomplete_data}getIncompleteData(){return this.has_last_incomplete_data?this.data_.subarray(this.current_syncword_offset_):null}}class sa{constructor(e){this.data_=e,this.eof_flag_=!1,this.current_syncword_offset_=this.findNextSyncwordOffset(0),this.eof_flag_&&console.error("Could not found ADTS syncword until payload end")}findNextSyncwordOffset(e){let t=e,i=this.data_;for(;;){if(t+1>=i.byteLength)return this.eof_flag_=!0,i.byteLength;if(695===(i[t+0]<<3|i[t+1]>>>5))return t;t++}}getLATMValue(e){let t=e.readBits(2),i=0;for(let s=0;s<=t;s++)i<<=8,i|=e.readByte();return i}readNextAACFrame(e){let t=this.data_,i=null;for(;null==i&&!this.eof_flag_;){let s=this.current_syncword_offset_,r=(31&t[s+1])<<8|t[s+2];if(s+3+r>=this.data_.byteLength){this.eof_flag_=!0,this.has_last_incomplete_data=!0;break}let a=new Gr(t.subarray(s+3,s+3+r)),o=null;if(a.readBool()){if(null==e){console.warn("StreamMuxConfig Missing"),this.current_syncword_offset_=this.findNextSyncwordOffset(s+3+r),a.destroy();continue}o=e}else{let e=a.readBool();if(e&&a.readBool()){console.error("audioMuxVersionA is Not Supported"),a.destroy();break}if(e&&this.getLATMValue(a),!a.readBool()){console.error("allStreamsSameTimeFraming zero is Not Supported"),a.destroy();break}if(0!==a.readBits(6)){console.error("more than 2 numSubFrames Not Supported"),a.destroy();break}if(0!==a.readBits(4)){console.error("more than 2 numProgram Not Supported"),a.destroy();break}if(0!==a.readBits(3)){console.error("more than 2 numLayer Not Supported"),a.destroy();break}let t=e?this.getLATMValue(a):0,i=a.readBits(5);t-=5;let s=a.readBits(4);t-=4;let r=a.readBits(4);t-=4,a.readBits(3),t-=3,t>0&&a.readBits(t);let n=a.readBits(3);if(0!==n){console.error(`frameLengthType = ${n}. Only frameLengthType = 0 Supported`),a.destroy();break}a.readByte();let l=a.readBool();if(l)if(e)this.getLATMValue(a);else{let e=0;for(;;){e<<=8;let t=a.readBool();if(e+=a.readByte(),!t)break}console.log(e)}a.readBool()&&a.readByte(),o={},o.audio_object_type=i,o.sampling_freq_index=s,o.sampling_frequency=$r[o.sampling_freq_index],o.channel_config=r,o.other_data_present=l}let n=0;for(;;){let e=a.readByte();if(n+=e,255!==e)break}let l=new Uint8Array(n);for(let e=0;e1&&void 0!==arguments[1]?arguments[1]:0;return(e[t]<<24>>>0)+(e[t+1]<<16)+(e[t+2]<<8)+(e[t+3]||0)}function aa(e){const t=e.byteLength,i=new Uint8Array(4);i[0]=t>>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t;const s=new Uint8Array(t+4);return s.set(i,0),s.set(e,4),s}function oa(){}function na(e){let t=null;const i=["webgl","experimental-webgl","moz-webgl","webkit-3d"];let s=0;for(;!t&&s0&&void 0!==arguments[0]?arguments[0]:"";const t=e.split(","),i=atob(t[1]),s=t[0].replace("data:","").replace(";base64","");let r=i.length,a=new Uint8Array(r);for(;r--;)a[r]=i.charCodeAt(r);return new File([a],"file",{type:s})}function ha(){return(new Date).getTime()}function ca(e,t,i){return Math.max(Math.min(e,Math.max(t,i)),Math.min(t,i))}function ua(e,t,i){if(e)return"object"==typeof t&&Object.keys(t).forEach((i=>{ua(e,i,t[i])})),e.style[t]=i,e}function pa(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(!e)return 0;const s=getComputedStyle(e,null).getPropertyValue(t);return i?parseFloat(s):s}function fa(){return performance&&"function"==typeof performance.now?performance.now():Date.now()}function ma(e){let t=0,i=fa();return s=>{if(!Da(s))return;t+=s;const r=fa(),a=r-i;a>=1e3&&(e(t/a*1e3),i=r,t=0)}}(()=>{try{if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate){const e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}})();const ga='"4-1-2024"';function ya(){return/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase())}function Aa(){return!(ya()||/ipad|android(?!.*mobile)|tablet|kindle|silk/i.test(window.navigator.userAgent.toLowerCase()))}function ba(){const e=window.navigator.userAgent.toLowerCase();return/android/i.test(e)}function _a(){const e=window.navigator.userAgent.toLowerCase();return/firefox/i.test(e)}function va(){const e=window.navigator.userAgent.toLowerCase()||"",t={type:"",version:""},i={IE:window.ActiveXObject||"ActiveXObject"in window,Chrome:e.indexOf("chrome")>-1&&e.indexOf("safari")>-1,Firefox:e.indexOf("firefox")>-1,Opera:e.indexOf("opera")>-1,Safari:e.indexOf("safari")>-1&&-1==e.indexOf("chrome"),Edge:e.indexOf("edge")>-1,QQBrowser:/qqbrowser/.test(e),WeixinBrowser:/MicroMessenger/i.test(e)};for(let s in i)if(i[s]){let i="";if("IE"===s){const t=e.match(/(msie\s|trident.*rv:)([\w.]+)/);t&&t.length>2&&(i=e.match(/(msie\s|trident.*rv:)([\w.]+)/)[2])}else if("Chrome"===s){for(let e in navigator.mimeTypes)"application/360softmgrplugin"===navigator.mimeTypes[e].type&&(s="360");const t=e.match(/chrome\/([\d.]+)/);t&&t.length>1&&(i=t[1])}else if("Firefox"===s){const t=e.match(/firefox\/([\d.]+)/);t&&t.length>1&&(i=t[1])}else if("Opera"===s){const t=e.match(/opera\/([\d.]+)/);t&&t.length>1&&(i=t[1])}else if("Safari"===s){const t=e.match(/version\/([\d.]+)/);t&&t.length>1&&(i=t[1])}else if("Edge"===s){const t=e.match(/edge\/([\d.]+)/);t&&t.length>1&&(i=t[1])}else if("QQBrowser"===s){const t=e.match(/qqbrowser\/([\d.]+)/);t&&t.length>1&&(i=t[1])}t.type=s,t.version=parseInt(i)}return t}function wa(){const e=window.navigator.userAgent.toLowerCase();return e&&/iphone|ipad|ipod|ios/.test(e)}function Sa(){const e=window.navigator.userAgent;return!e.match(/Chrome/gi)&&!!e.match(/Safari/gi)}function Ta(e,t){if(0===arguments.length)return null;var i,s=t||"{y}-{m}-{d} {h}:{i}:{s}";"object"==typeof e?i=e:(10===(""+e).length&&(e=1e3*parseInt(e)),e=+e,i=new Date(e));var r={y:i.getFullYear(),m:i.getMonth()+1,d:i.getDate(),h:i.getHours(),i:i.getMinutes(),s:i.getSeconds(),a:i.getDay()},a=s.replace(/{(y|m|d|h|i|s|a)+}/g,((e,t)=>{var i=r[t];return"a"===t?["一","二","三","四","五","六","日"][i-1]:(e.length>0&&i<10&&(i="0"+i),i||0)}));return a}function Ea(){return"VideoFrame"in window}function ka(e){if("string"!=typeof e)return e;var t=Number(e);return isNaN(t)?e:t}function Ca(){return"xxxxxxxxxxxx4xxx".replace(/[xy]/g,(function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)}))}function xa(e,t){let i,s,r=!1;return function a(){for(var o=arguments.length,n=new Array(o),l=0;l{r=!1,i&&(a.apply(s,i),i=null,s=null)}),t)}}function Ra(e){if(null==e||""==e)return"0 Bytes";const t=new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB");let i=0;const s=parseFloat(e);i=Math.floor(Math.log(s)/Math.log(1024));var r=s/Math.pow(1024,i);return(r=r.toFixed(2))+t[i]}function Da(e){return"[object Number]"===Object.prototype.toString.call(e)}function La(){let e=!1;return"MediaSource"in self&&(self.MediaSource.isTypeSupported(ui)||self.MediaSource.isTypeSupported(pi)||self.MediaSource.isTypeSupported(fi)||self.MediaSource.isTypeSupported(mi)||self.MediaSource.isTypeSupported(gi))&&(e=!0),e}function Ia(){const e=va();return"chrome"===e.type.toLowerCase()&&e.version>=107}function Pa(){let e=!1;return"MediaStreamTrackGenerator"in window&&(e=!0),e}function Ba(){let e=!1;return"MediaStream"in window&&(e=!0),e}function Ma(e,t){let i=window.URL.createObjectURL(t),s=window.document.createElement("a");s.download=e,s.href=i;let r=window.document.createEvent("MouseEvents");r.initEvent("click",!0,!0),s.dispatchEvent(r),setTimeout((()=>{window.URL.revokeObjectURL(i)}),wa()?1e3:0)}function Ua(e){return null==e}function Fa(e){return!0===e||!1===e}function Oa(e){return!Ua(e)}function Na(e){let t={left:"",right:"",top:"",bottom:"",opacity:1,backgroundColor:"",image:{src:"",width:"100",height:"60"},text:{content:"",fontSize:"14",color:"#000",width:"",height:""},rect:{color:"green",lineWidth:2,width:"",height:"",fill:"",fillOpacity:.2},line:{x1:"",y1:"",x2:"",y2:"",color:"green",lineWidth:2},polygon:{color:"green",lineWidth:2,list:[],fill:"",fillOpacity:.2},html:""};const i=Object.assign(t.image,e.image||{}),s=Object.assign(t.text,e.text||{}),r=Object.assign(t.rect,e.rect||{}),a=Object.assign(t.line,e.line||{});return t=Object.assign(t,e,{image:i,text:s,rect:r,line:a}),t}function ja(e,t){let i={container:e||"",text:"",opacity:"",angle:"",color:"",fontSize:"",fontFamily:""};return i=Object.assign(i,t),{watermark_parent_node:i.container,watermark_alpha:i.opacity,watermark_angle:i.angle,watermark_fontsize:i.fontSize,watermark_color:i.color,watermark_font:i.fontFamily,watermark_txt:i.text}}function Ga(e,t){return new Promise(((i,s)=>{let r=Na(t);if(!r.image.src&&!r.text.content)return i(e);let a=document.createElement("canvas");a.width=t.width,a.height=t.height;let o=a.getContext("2d"),n=0,l=0;Da(r.left)?n=r.left:Da(r.right)&&(n=a.width-r.right),Da(r.top)?l=r.top:Da(r.bottom)&&(l=a.height-r.bottom);const d=new Image;d.src=e,d.onload=()=>{if(o.drawImage(d,0,0),r.image&&r.image.src){const e=new Image;e.src=r.image.src,e.setAttribute("crossOrigin","Anonymous"),e.onload=()=>{n-=r.image.width,o.drawImage(e,n,l,r.image.width,r.image.height),i(a.toDataURL(t.format,t.quality))},e.onerror=e=>{s()}}else r.text&&r.text.content&&(o.font=r.text.fontSize+"px 宋体",o.fillStyle=r.text.color,o.textAlign="right",o.fillText(r.text.content,n,l),i(a.toDataURL(t.format,t.quality)))},d.onerror=e=>{s(e)}}))}function za(e){var t;if(e>-1){var i=Math.floor(e/3600),s=Math.floor(e/60)%60,r=e%60;t=i<10?"0"+i+":":i+":",s<10&&(t+="0"),t+=s+":",(r=Math.round(r))<10&&(t+="0"),t+=r.toFixed(0)}return t}function $a(e,t){let i="";if(e>-1){const s=Math.floor(e/60)%60;let r=e%60;r=Math.round(r),i=s<10?"0"+s+":":s+":",r<10&&(i+="0"),i+=r,Ua(t)||(t<10&&(t="0"+t),i+=":"+t)}return i}function Ha(e){let t="";if(e>-1){const i=Math.floor(e/60/60)%60;let s=Math.floor(e/60)%60,r=e%60;s=Math.round(s),t=i<10?"0"+i+":":i+":",s<10&&(t+="0"),t+=s+":",r<10&&(t+="0"),t+=r}return t}function Va(e,t){const i=Math.floor(t/60)%60,s=Math.floor(t%60);return new Date(e).setHours(i,s,0,0)}function Wa(e,t){const i=Math.floor(t/60/60)%60,s=Math.floor(t/60)%60,r=t%60;return new Date(e).setHours(i,s,r,0)}function Ja(e){return(""+e).length}function qa(e){return e&&0===Object.keys(e).length}function Ka(e){return!qa(e)}function Ya(e){return"string"==typeof e}const Qa=()=>{const e=window.navigator.userAgent;return/MicroMessenger/i.test(e)},Xa=()=>{const e=window.navigator.userAgent;return/Chrome/i.test(e)};function Za(e){const t=e||window.event;return t.target||t.srcElement}function eo(){return _a()&&function(){const e=navigator.userAgent.toLowerCase();return/macintosh|mac os x/i.test(e)}()}function to(e){return"function"==typeof e}function io(e){if(ya()){let t=0,i=0;if(1===e.touches.length){let s=e.touches[0];t=s.clientX,i=s.clientY}return{posX:t,posY:i}}let t=0,i=0;const s=e||window.event;return s.pageX||s.pageY?(t=s.pageX,i=s.pageY):(s.clientX||s.clientY)&&(t=e.clientX+document.documentElement.scrollLeft+document.body.scrollLeft,i=e.clientY+document.documentElement.scrollTop+document.body.scrollTop),{posX:t,posY:i}}function so(){let e=document.createElement("video"),t=e.canPlayType("application/vnd.apple.mpegurl");return e=null,t}function ro(e){let t=Ao(e.hasAudio)&&(e.useMSE||e.useWCS&&!e.useOffscreen)&&Ao(e.demuxUseWorker);return!!(Ao(t)&&e.useMSE&&e.mseDecodeAudio&&Ao(e.demuxUseWorker))||t}function ao(e){const t=e.toString().trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1];const i=new Blob([t],{type:"application/javascript"});return URL.createObjectURL(i)}function oo(e){e.close()}function no(){return"https:"===window.location.protocol||"localhost"===window.location.hostname}function lo(e){const t=Object.prototype.toString;return function(e){switch(t.call(e)){case"[object Error]":case"[object Exception]":case"[object DOMException]":return!0;default:try{return e instanceof Error}catch(e){return!1}}}(e)?e.message:null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function ho(e,t){t&&(e=e.filter((e=>e.type&&e.type===t)));let i=e[0],s=null,r=1;if(e.length>0){let t=e[1];t&&t.ts-i.ts>1e5&&(i=t,r=2)}if(i)for(let a=r;a=1e3){e[a-1].ts-i.ts<1e3&&(s=a+1)}}}return s}function co(e){for(var t=(e+"=".repeat((4-e.length%4)%4)).replace(/\-/g,"+").replace(/_/g,"/"),i=window.atob(t),s=new Uint8Array(i.length),r=0;r>4===ws&&e[1]===vs}function yo(e){return!0===e||"true"===e}function Ao(e){return!0!==e&&"true"!==e}function bo(e,t,i){e&&(e.dataset?e.dataset[t]=i:e.setAttribute("data-"+t,i))}function _o(e,t){return e?e.dataset?e.dataset[t]:e.getAttribute("data-"+t):""}function vo(e){return e.replace(/-([a-z])/g,(function(e,t){return t.toUpperCase()}))}function wo(){return/iphone/i.test(navigator.userAgent)}function So(){return window.performance&&window.performance.memory?window.performance.memory:null}function To(){try{var e=document.createElement("canvas");return!(!window.WebGL2RenderingContext||!e.getContext("webgl2"))}catch(e){return!1}}function Eo(e){return e.trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1]}function ko(){let e=!1;return"requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e=!0),e}function Co(){let e=!1;return"PressureObserver"in window&&(e=!0),e}class xo{constructor(e){this.destroys=[],this.proxy=this.proxy.bind(this),this.master=e}proxy(e,t,i){let s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};if(!e)return;if(Array.isArray(t))return t.map((t=>this.proxy(e,t,i,s)));e.addEventListener(t,i,s);const r=()=>{to(e.removeEventListener)&&e.removeEventListener(t,i,s)};return this.destroys.push(r),r}destroy(){this.master.debug&&this.master.debug.log("Events","destroy"),this.destroys.forEach((e=>e())),this.destroys=[]}}var Ro=1e-6,Do="undefined"!=typeof Float32Array?Float32Array:Array;function Lo(){var e=new Do(16);return Do!=Float32Array&&(e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0),e[0]=1,e[5]=1,e[10]=1,e[15]=1,e}function Io(e){return e[0]=1,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=1,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}Math.hypot||(Math.hypot=function(){for(var e=0,t=arguments.length;t--;)e+=arguments[t]*arguments[t];return Math.sqrt(e)});var Po,Bo=function(e,t,i,s,r,a,o){var n=1/(t-i),l=1/(s-r),d=1/(a-o);return e[0]=-2*n,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=-2*l,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=2*d,e[11]=0,e[12]=(t+i)*n,e[13]=(r+s)*l,e[14]=(o+a)*d,e[15]=1,e};function Mo(e,t,i){var s=new Do(3);return s[0]=e,s[1]=t,s[2]=i,s}Po=new Do(3),Do!=Float32Array&&(Po[0]=0,Po[1]=0,Po[2]=0);class Uo{constructor(e,t){this.gl=e,t&&this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1);const i=this._initShaderProgram();this._programInfo={program:i,attribLocations:{vertexPosition:e.getAttribLocation(i,"aVertexPosition"),texturePosition:e.getAttribLocation(i,"aTexturePosition")},uniformLocations:{projectionMatrix:e.getUniformLocation(i,"uProjectionMatrix"),modelMatrix:e.getUniformLocation(i,"uModelMatrix"),viewMatrix:e.getUniformLocation(i,"uViewMatrix"),rgbatexture:e.getUniformLocation(i,"rgbaTexture"),ytexture:e.getUniformLocation(i,"yTexture"),utexture:e.getUniformLocation(i,"uTexture"),vtexture:e.getUniformLocation(i,"vTexture"),isyuv:e.getUniformLocation(i,"isyuv")}},this._buffers=this._initBuffers(),this._rgbatexture=this._createTexture(),this._ytexture=this._createTexture(),this._utexture=this._createTexture(),this._vtexture=this._createTexture()}destroy(){this.gl.deleteProgram(this._programInfo.program),this.gl.deleteBuffer(this._buffers.position),this.gl.deleteBuffer(this._buffers.texPosition),this.gl.deleteBuffer(this._buffers.indices),this.gl.deleteTexture(this._rgbatexture),this.gl.deleteTexture(this._ytexture),this.gl.deleteTexture(this._utexture),this.gl.deleteTexture(this._vtexture),this._programInfo=null,this._buffers=null,this._rgbatexture=null,this._ytexture=null,this._utexture=null,this._vtexture=null}_initShaderProgram(){const e=this._loadShader(this.gl.VERTEX_SHADER,"\n attribute vec4 aVertexPosition;\n attribute vec2 aTexturePosition;\n varying lowp vec2 vTexturePosition;\n void main(void) {\n gl_Position = aVertexPosition;\n vTexturePosition = aTexturePosition;\n }\n "),t=this._loadShader(this.gl.FRAGMENT_SHADER,"\n precision highp float;\n varying highp vec2 vTexturePosition;\n uniform int isyuv;\n uniform sampler2D rgbaTexture;\n uniform sampler2D yTexture;\n uniform sampler2D uTexture;\n uniform sampler2D vTexture;\n\n const mat4 YUV2RGB = mat4( 1.1643828125, 0, 1.59602734375, -.87078515625,\n 1.1643828125, -.39176171875, -.81296875, .52959375,\n 1.1643828125, 2.017234375, 0, -1.081390625,\n 0, 0, 0, 1);\n\n\n void main(void) {\n\n if (isyuv>0) {\n\n highp float y = texture2D(yTexture, vTexturePosition).r;\n highp float u = texture2D(uTexture, vTexturePosition).r;\n highp float v = texture2D(vTexture, vTexturePosition).r;\n gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;\n\n } else {\n gl_FragColor = texture2D(rgbaTexture, vTexturePosition);\n }\n }\n "),i=this.gl.createProgram();return this.gl.attachShader(i,e),this.gl.attachShader(i,t),this.gl.linkProgram(i),this.gl.getProgramParameter(i,this.gl.LINK_STATUS)?i:(console.log("Unable to initialize the shader program: "+this.gl.getProgramInfoLog(i)),null)}_loadShader(e,t){const i=this.gl,s=i.createShader(e);return i.shaderSource(s,t),i.compileShader(s),i.getShaderParameter(s,i.COMPILE_STATUS)?s:(console.log("An error occurred compiling the shaders: "+i.getShaderInfoLog(s)),i.deleteShader(s),null)}_initBuffers(){const e=this.gl,t=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,t);const i=[-1,-1,1,-1,1,1,-1,1];e.bufferData(e.ARRAY_BUFFER,new Float32Array(i),e.STATIC_DRAW);var s=[];s=s.concat([0,1],[1,1],[1,0],[0,0]);const r=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array(s),e.STATIC_DRAW);const a=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,a);return e.bufferData(e.ELEMENT_ARRAY_BUFFER,new Uint16Array([0,1,2,0,2,3]),e.STATIC_DRAW),{positions:i,position:t,texPosition:r,indices:a}}_createTexture(){let e=this.gl.createTexture();return this.gl.bindTexture(this.gl.TEXTURE_2D,e),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),e}_drawScene(e,t,i){this.gl.viewport(0,0,e,t),this.gl.enable(this.gl.BLEND),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this._buffers.position),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(this._buffers.positions),this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this._programInfo.attribLocations.vertexPosition,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this._programInfo.attribLocations.vertexPosition),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this._buffers.texPosition),this.gl.vertexAttribPointer(this._programInfo.attribLocations.texturePosition,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this._programInfo.attribLocations.texturePosition),this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this._buffers.indices);i?(this.gl.activeTexture(this.gl.TEXTURE0+3),this.gl.bindTexture(this.gl.TEXTURE_2D,this._ytexture),this.gl.activeTexture(this.gl.TEXTURE0+4),this.gl.bindTexture(this.gl.TEXTURE_2D,this._utexture),this.gl.activeTexture(this.gl.TEXTURE0+5),this.gl.bindTexture(this.gl.TEXTURE_2D,this._vtexture)):(this.gl.activeTexture(this.gl.TEXTURE0+2),this.gl.bindTexture(this.gl.TEXTURE_2D,this._rgbatexture)),this.gl.useProgram(this._programInfo.program),this.gl.uniform1i(this._programInfo.uniformLocations.rgbatexture,2),this.gl.uniform1i(this._programInfo.uniformLocations.ytexture,3),this.gl.uniform1i(this._programInfo.uniformLocations.utexture,4),this.gl.uniform1i(this._programInfo.uniformLocations.vtexture,5),this.gl.uniform1i(this._programInfo.uniformLocations.isyuv,i?1:0),this.gl.drawElements(this.gl.TRIANGLES,6,this.gl.UNSIGNED_SHORT,0)}_calRect(e,t,i,s,r,a){let o=2*e/r-1,n=2*(a-t-s)/a-1,l=2*(e+i)/r-1,d=2*(a-t)/a-1;return[o,n,l,n,l,d,o,d]}_clear(){this.gl.clearColor(0,0,0,1),this.gl.clearDepth(1),this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT)}render(e,t,i,s,r){const a=this.gl;this._clear(),a.activeTexture(a.TEXTURE0),a.bindTexture(a.TEXTURE_2D,this._ytexture),a.texImage2D(a.TEXTURE_2D,0,a.LUMINANCE,e,t,0,a.LUMINANCE,a.UNSIGNED_BYTE,i),a.activeTexture(a.TEXTURE1),a.bindTexture(a.TEXTURE_2D,this._utexture),a.texImage2D(a.TEXTURE_2D,0,a.LUMINANCE,e/2,t/2,0,a.LUMINANCE,a.UNSIGNED_BYTE,s),a.activeTexture(a.TEXTURE2),a.bindTexture(a.TEXTURE_2D,this._vtexture),a.texImage2D(a.TEXTURE_2D,0,a.LUMINANCE,e/2,t/2,0,a.LUMINANCE,a.UNSIGNED_BYTE,r),this._buffers.positions=[-1,-1,1,-1,1,1,-1,1],this._drawScene(e,t,!0)}renderYUV(e,t,i){let s=i.slice(0,e*t),r=i.slice(e*t,e*t*5/4),a=i.slice(e*t*5/4,e*t*3/2);const o=this.gl;this._clear(),o.activeTexture(o.TEXTURE0),o.bindTexture(o.TEXTURE_2D,this._ytexture),o.texImage2D(o.TEXTURE_2D,0,o.LUMINANCE,e,t,0,o.LUMINANCE,o.UNSIGNED_BYTE,s),o.activeTexture(o.TEXTURE1),o.bindTexture(o.TEXTURE_2D,this._utexture),o.texImage2D(o.TEXTURE_2D,0,o.LUMINANCE,e/2,t/2,0,o.LUMINANCE,o.UNSIGNED_BYTE,r),o.activeTexture(o.TEXTURE2),o.bindTexture(o.TEXTURE_2D,this._vtexture),o.texImage2D(o.TEXTURE_2D,0,o.LUMINANCE,e/2,t/2,0,o.LUMINANCE,o.UNSIGNED_BYTE,a),this._buffers.positions=[-1,-1,1,-1,1,1,-1,1],this._drawScene(e,t,!0)}drawDom(e,t,i,s,r){const a=this.gl;a.activeTexture(a.TEXTURE0),a.bindTexture(a.TEXTURE_2D,this._rgbatexture),a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,r),this._buffers.positions=this._calRect(i,s,r.width,r.height,e,t),this._drawScene(e,t,!1)}}class Fo{constructor(e){this.gpu=e,this.pipeline=null,this.matrixGroupInfo=null,this.depthTexture=null,this.textureGroupInfo=null,this.hasInited=!1,this.buffers=this._initBuffer(),this._initPipeline().then((e=>{this.pipeline=e,this.matrixGroupInfo=this._initMatrixGroupInfo(),this.hasInited=!0}))}destroy(){this.gpu&&(this.gpu.device.destroy(),this.gpu=null),this.hasInited=!1,this.pipeline=null,this.matrixGroupInfo=null,this.depthTexture=null,this.textureGroupInfo=null}_initBuffer(){const e=this.gpu.device,t=new Float32Array([-1,-1,-1,1,-1,-1,1,1,-1,-1,1,-1]),i=e.createBuffer({size:t.byteLength,usage:window.GPUBufferUsage.VERTEX|window.GPUBufferUsage.COPY_DST});e.queue.writeBuffer(i,0,t);const s=new Float32Array([0,1,1,1,1,0,0,0]),r=e.createBuffer({size:s.byteLength,usage:window.GPUBufferUsage.VERTEX|window.GPUBufferUsage.COPY_DST});e.queue.writeBuffer(r,0,s);const a=new Uint16Array([0,1,2,0,2,3]),o=e.createBuffer({size:a.byteLength,usage:window.GPUBufferUsage.INDEX|window.GPUBufferUsage.COPY_DST});return e.queue.writeBuffer(o,0,a),{positionBuffer:i,texpositionBuffer:r,indexBuffer:o}}_initPipeline(){return new Promise(((e,t)=>{const i=this.gpu.device,s=this.gpu.format,r={layout:"auto",vertex:{module:i.createShaderModule({code:"\n\n @binding(0) @group(0) var uModelMatrix : mat4x4;\n @binding(1) @group(0) var uViewMatrix : mat4x4;\n @binding(2) @group(0) var uProjectionMatrix : mat4x4;\n\n struct VertexOutput {\n @builtin(position) Position : vec4,\n @location(0) vTexturePosition : vec2,\n }\n\n @vertex\n fn main(\n @location(0) aVertexPosition : vec4,\n @location(1) aTexturePosition : vec2\n ) -> VertexOutput {\n var output : VertexOutput;\n var tmppos : vec4 = uProjectionMatrix * uViewMatrix * uModelMatrix * aVertexPosition;\n output.Position = vec4(tmppos.x, tmppos.y, (tmppos.z+1.)/2., tmppos.w); // webgl z [-1, 1], webgpu z [0, 1], 这里z做下调整 z-webgpu = (z-webgl+1)/2\n output.vTexturePosition = aTexturePosition;\n return output;\n }\n\n "}),entryPoint:"main",buffers:[{arrayStride:12,attributes:[{shaderLocation:0,offset:0,format:"float32x3"}]},{arrayStride:8,attributes:[{shaderLocation:1,offset:0,format:"float32x2"}]}]},primitive:{topology:"triangle-list"},fragment:{module:i.createShaderModule({code:"\n @group(1) @binding(0) var mySampler: sampler;\n @group(1) @binding(1) var yTexture: texture_2d;\n @group(1) @binding(2) var uTexture: texture_2d;\n @group(1) @binding(3) var vTexture: texture_2d;\n\n const YUV2RGB : mat4x4 = mat4x4( 1.1643828125, 0, 1.59602734375, -.87078515625,\n 1.1643828125, -.39176171875, -.81296875, .52959375,\n 1.1643828125, 2.017234375, 0, -1.081390625,\n 0, 0, 0, 1);\n\n @fragment\n fn main(\n @location(0) vTexturePosition: vec2\n ) -> @location(0) vec4 {\n\n var y : f32= textureSample(yTexture, mySampler, vTexturePosition).r;\n var u : f32 = textureSample(uTexture, mySampler, vTexturePosition).r;\n var v : f32 = textureSample(vTexture, mySampler, vTexturePosition).r;\n\n return vec4(y, u, v, 1.0)*YUV2RGB;\n }\n\n "}),entryPoint:"main",targets:[{format:s}]},depthStencil:{depthWriteEnabled:!0,depthCompare:"less",format:"depth24plus"}};i.createRenderPipelineAsync(r).then((t=>{e(t)})).catch((e=>{t(e)}))}))}_initMatrixGroupInfo(){const e=this.gpu.device,t=this.pipeline,i=Lo();Bo(i,-1,1,-1,1,.1,100);const s=Lo();Io(s);const r=Lo();!function(e,t,i,s){var r,a,o,n,l,d,h,c,u,p,f=t[0],m=t[1],g=t[2],y=s[0],A=s[1],b=s[2],_=i[0],v=i[1],w=i[2];Math.abs(f-_)Na(e)));this.configList=i,this._updateDom()}_resizeDomForVideo(){const e=this.player.width,t=this.player.height,i=this.player.getVideoInfo();if(!(i&&i.height>0&&i.width>0))return;let s=i.width,r=i.height;const a=this.player._opt;let o=t,n=e;if(a.hasControl&&!a.controlAutoHide){const e=a.playType===w?Qt:Yt;ya()&&this.player.fullscreen&&a.useWebFullScreen?n-=e:o-=e}const l=a.rotate;let d=(n-s)/2,h=(o-r)/2;270!==l&&90!==l||(s=i.height,r=i.width);const c=n/s,u=o/r;let p=c>u?u:c;a.isResize||c!==u&&(p=c+","+u),a.isFullResize&&(p=c>u?c:u);let f="scale("+p+")";"none"===a.mirrorRotate&&l&&(f+=" rotate("+l+"deg)"),"level"===a.mirrorRotate?f+=" rotateY(180deg)":"vertical"===a.mirrorRotate&&(f+=" rotateX(180deg)"),this.scale=-1!==(""+p).indexOf(",")?c:p,this.shadowRootInnerDom.style.transform=f,this.shadowRootInnerDom.style.left=d+"px",this.shadowRootInnerDom.style.top=h+"px",this.shadowRootInnerDom.style.width=i.width+"px",this.shadowRootInnerDom.style.height=i.height+"px",this.shadowRootInnerDom.style.display="block"}_resizeDomForCanvas(){const e=this.player.getVideoInfo();if(!(e&&e.height>0&&e.width>0))return;const t=this.player._opt;let i=this.player.width,s=this.player.height;if(t.hasControl&&!t.controlAutoHide){const e=t.playType===w?Qt:Yt;ya()&&this.player.fullscreen&&t.useWebFullScreen?i-=e:s-=e}let r=e.width,a=e.height;const o=t.rotate;let n=(i-r)/2,l=(s-a)/2;270!==o&&90!==o||(r=e.height,a=e.width);const d=i/r,h=s/a;let c=d>h?h:d;t.isResize||d!==h&&(c=d+","+h),t.isFullResize&&(c=d>h?d:h);let u="scale("+c+")";"none"===t.mirrorRotate&&o&&(u+=" rotate("+o+"deg)"),"level"===t.mirrorRotate?u+=" rotateY(180deg)":"vertical"===t.mirrorRotate&&(u+=" rotateX(180deg)"),this.shadowRootInnerDom.style.height=e.height+"px",this.shadowRootInnerDom.style.width=e.width+"px",this.shadowRootInnerDom.style.padding="0",this.shadowRootInnerDom.style.transform=u,this.shadowRootInnerDom.style.left=n+"px",this.shadowRootInnerDom.style.top=l+"px",this.shadowRootInnerDom.style.display="block"}_resizeDomRatio(){const e=this.player.getVideoInfo();if(!(e&&e.height>0&&e.width>0))return;const t=this.player._opt.aspectRatio.split(":").map(Number);let i=this.player.width,s=this.player.height;const r=this.player._opt;let a=0;r.hasControl&&!r.controlAutoHide&&(a=r.playType===w?Qt:Yt,s-=a);const o=e.width/e.height,n=t[0]/t[1];if(o>n){const t=n*e.height/e.width;this.shadowRootInnerDom.style.width=100*t+"%",this.shadowRootInnerDom.style.height=`calc(100% - ${a}px)`,this.shadowRootInnerDom.style.padding=`0 ${(i-i*t)/2}px`}else{const t=e.width/n/e.height;this.shadowRootInnerDom.style.width="100%",this.shadowRootInnerDom.style.height=`calc(${100*t}% - ${a}px)`,this.shadowRootInnerDom.style.padding=(s-s*t)/2+"px 0"}this.shadowRootInnerDom.style.display="block"}_updateDom(){this.shadowRoot&&this.configList.forEach((e=>{const t=document.createElement("div");let i=null;if(e.image&&e.image.src?(i=document.createElement("img"),i.style.height="100%",i.style.width="100%",i.style.objectFit="contain",i.src=e.image.src):e.text&&e.text.content?i=document.createTextNode(e.text.content):(e.rect&&e.rect.color&&e.rect.width||e.html||e.line&&e.line.x1&&e.line.y1&&e.line.x2&&e.line.y2||e.polygon&&e.polygon.list&&e.polygon.list.length>=3)&&(i=document.createElement("div")),i){if(t.appendChild(i),t.style.visibility="",t.style.position="absolute",t.style.display="block",t.style["-ms-user-select"]="none",t.style["-moz-user-select"]="none",t.style["-webkit-user-select"]="none",t.style["-o-user-select"]="none",t.style["user-select"]="none",t.style["-webkit-touch-callout"]="none",t.style["-webkit-tap-highlight-color"]="rgba(0,0,0,0)",t.style["-webkit-text-size-adjust"]="none",t.style["-webkit-touch-callout"]="none",t.style.opacity=e.opacity,Oa(e.left)&&(Da(e.left)?t.style.left=e.left+"px":t.style.left=e.left),Oa(e.right)&&(Da(e.right)?t.style.right=e.right+"px":t.style.right=e.right),Oa(e.top)&&(Da(e.top)?t.style.top=e.top+"px":t.style.top=e.top),Oa(e.bottom)&&(Da(e.bottom)?t.style.bottom=e.bottom+"px":t.style.bottom=e.bottom),e.backgroundColor&&(t.style.backgroundColor=e.backgroundColor),t.style.overflow="hidden",t.style.zIndex="9999999",e.image&&e.image.src)t.style.width=e.image.width+"px",t.style.height=e.image.height+"px";else if(e.text&&e.text.content)t.style.fontSize=e.text.fontSize+"px",t.style.color=e.text.color,e.text.width&&(t.style.width=e.text.width+"px"),e.text.height&&(t.style.height=e.text.height+"px");else if(e.rect&&e.rect.color&&e.rect.width){if(t.style.width=e.rect.width+"px",t.style.height=e.rect.height+"px",t.style.borderWidth=e.rect.lineWidth+"px",t.style.borderStyle="solid",t.style.borderColor=e.rect.color,e.rect.fill){const i=document.createElement("div");i.style.position="absolute",i.style.width="100%",i.style.height="100%",i.style.backgroundColor=e.rect.fill,e.rect.fillOpacity&&(i.style.opacity=e.rect.fillOpacity),t.appendChild(i)}}else if(e.html)t.style.width="100%",t.style.height="100%",t.innerHTML=e.html;else if(e.line&&e.line.x1&&e.line.y1&&e.line.x2&&e.line.y2)this.settingLine(t,e.line);else if(e.polygon&&e.polygon.list&&e.polygon.list.length>=3){t.style.width="100%",t.style.height="100%";let i=e.polygon.list;const s=e.polygon.color,r=e.polygon.lineWidth;if(i=i.sort(((e,t)=>(e.index||0)-(t.index||0))),e.polygon.fill){const s=document.createElement("div");s.style.position="absolute",s.style.width="100%",s.style.height="100%";const r="polygon("+i.map((e=>`${e.x}px ${e.y}px`)).join(", ")+")";s.style.clipPath=r,s.style.backgroundColor=e.polygon.fill,e.polygon.fillOpacity&&(s.style.opacity=e.polygon.fillOpacity),t.appendChild(s)}i.forEach(((e,a)=>{const o=document.createElement("div");if(a===i.length-1){const a=i[0],n={x1:e.x,y1:e.y,x2:a.x,y2:a.y,color:s,lineWidth:r};return this.settingLine(o,n),void t.appendChild(o)}const n=i[a+1],l={x1:e.x,y1:e.y,x2:n.x,y2:n.y,color:s,lineWidth:r};this.settingLine(o,l),t.appendChild(o)}))}this.isDynamic&&(this.shadowRootDynamicDom=t),this.shadowRootInnerDom.appendChild(t)}}))}settingLine(e,t){const i=t.x1,s=t.y1,r=t.x2,a=t.y2;var o=Math.sqrt((i-r)**2+(s-a)**2),n=180*Math.atan2(a-s,r-i)/Math.PI;e.style.backgroundColor=t.color,e.style.width=o+"px",e.style.height=t.lineWidth+"px",e.style.position="absolute",e.style.top=s+"px",e.style.left=i+"px",e.style.transform="rotate("+n+"deg)",e.style.transformOrigin="0 0"}remove(){this._removeDom()}_removeDom(){this.shadowRootInnerDom&&(this.shadowRootInnerDom.innerHTML="")}}class No extends a{constructor(){super(),this.videoInfo={width:null,height:null,encType:null,encTypeCode:null},this.init=!1,this.prevAiFaceDetectTime=null,this.prevAiObjectDetectTime=null,this.prevOcclusionDetectTime=null,this.contentWatermark=null,this.aiContentWatermark=null,this.tempContentList=[],this.tempAiContentList=[],this.streamFps=0}destroy(){this.resetInit(),this.contentWatermark&&(this.contentWatermark.destroy(),this.contentWatermark=null),this.tempContentList=[],this.aiContentWatermark&&(this.aiContentWatermark.destroy(),this.aiContentWatermark=null),this.tempAiContentList=[],this.prevAiFaceDetectTime=null,this.prevAiObjectDetectTime=null,this.streamFps=0,this.off()}resetInit(){this.videoInfo={width:null,height:null,encType:null,encTypeCode:null},this.init=!1}getHasInit(){return this.init}updateVideoInfo(e){Oa(e.encTypeCode)&&(this.videoInfo.encType=_t[e.encTypeCode],this.videoInfo.encTypeCode=e.encTypeCode),Oa(e.encType)&&(this.videoInfo.encType=e.encType),Oa(e.width)&&(this.videoInfo.width=e.width),Oa(e.height)&&(this.videoInfo.height=e.height),Oa(this.videoInfo.encType)&&Oa(this.videoInfo.height)&&Oa(this.videoInfo.width)&&!this.init&&(this.player.emit(lt.videoInfo,this.videoInfo),this.init=!0)}getVideoInfo(){return this.videoInfo}clearView(){this.tempContentList=[],this.tempAiContentList=[]}resize(){if(this.player.debug.log("CommonVideo","resize()"),"default"===this.player._opt.aspectRatio||ya()?this._resize():this._resizeRatio(),this.contentWatermark&&this.contentWatermark.resize(),this.aiContentWatermark&&this.aiContentWatermark.resize(),this.player.singleWatermark&&this.player.singleWatermark.resize(),this.player.ghostWatermark&&this.player.ghostWatermark.resize(),this.player.dynamicWatermark&&this.player.dynamicWatermark.resize(),this.player.zoom&&this.player.zooming){const e=this._getStyleScale();this.player.zoom.updatePrevVideoElementStyleScale(e),this.player.zoom.updateVideoElementScale()}}_resizeRatio(){this.player.debug.log("CommonVideo","_resizeRatio()");const e=this.player._opt.aspectRatio.split(":").map(Number);let t=this.player.width,i=this.player.height;const s=this.player._opt;let r=0;s.hasControl&&!s.controlAutoHide&&(r=s.playType===w?Qt:Yt,i-=r);const a=this.videoInfo,o=a.width/a.height,n=e[0]/e[1];if(this.getType()===W&&(this.$videoElement.style.left="0",this.$videoElement.style.top="0",this.$videoElement.style.transform="none"),this.getType()===J&&this.player._opt.videoRenderSupportScale&&(this.$videoElement.style.objectFit="fill"),o>n){const e=n*a.height/a.width;this.$videoElement.style.width=100*e+"%",this.$videoElement.style.height=`calc(100% - ${r}px)`,this.$videoElement.style.padding=`0 ${(t-t*e)/2}px`}else{const e=a.width/n/a.height;this.$videoElement.style.width="100%",this.$videoElement.style.height=`calc(${100*e}% - ${r}px)`,this.$videoElement.style.padding=(i-i*e)/2+"px 0"}}play(){}pause(){}setRate(e){}getType(){return""}getCanvasType(){return""}getCurrentTime(){return 0}getStreamFps(){return this.streamFps}isPlaying(){return!0}getPlaybackQuality(){return null}setStreamFps(e){this.player.debug.log("CommonVideo","setStreamFps",e),this.streamFps=e}addContentToCanvas(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.tempContentList=e}addAiContentToCanvas(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.tempAiContentList=e}doAddContentToWatermark(){if(this.tempContentList.length>0){this.contentWatermark||(this.contentWatermark=new Oo(this.player),this.contentWatermark.resize());const e=[];this.tempContentList.forEach((t=>{let i={left:t.x||0,top:t.y||0};"text"===t.type?i.text={content:t.text,fontSize:t.fontSize||"14",color:t.color||"#000"}:"rect"===t.type?i.rect={width:t.width,height:t.height,color:t.color||"green",lineWidth:t.lineWidth||2,fill:t.fill||"",fillOpacity:t.fillOpacity||.2}:"polygon"===t.type?i.polygon={list:t.list,color:t.color||"green",lineWidth:t.lineWidth||2,fill:t.fill,fillOpacity:t.fillOpacity||.2}:"line"===t.type&&(i.line={color:t.color||"green",lineWidth:t.lineWidth||2,x1:t.x1,y1:t.y1,x2:t.x2,y2:t.y2}),e.push(i)})),this.contentWatermark.update(e)}else this.contentWatermark&&this.contentWatermark.remove()}doAddAiContentToWatermark(){if(this.tempAiContentList.length>0){this.aiContentWatermark||(this.aiContentWatermark=new Oo(this.player),this.aiContentWatermark.resize());const e=this.tempAiContentList.map((e=>{let t={left:e.x,top:e.y};return"text"===e.type?t.text={content:e.text,fontSize:e.fontSize,color:e.color}:"rect"===e.type&&(t.rect={width:e.width,height:e.height,color:e.color,lineWidth:e.lineWidth}),t}));this.aiContentWatermark.update(e)}else this.aiContentWatermark&&this.aiContentWatermark.remove()}_getStyleScale(){let e=this.$videoElement.style.transform.match(/scale\([0-9., ]*\)/g),t="";if(e&&e[0]){t=e[0].replace("scale(","").replace(")","").split(",")}return t}getReadyStateInited(){return!0}}var jo="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0;function Go(e,t,i){var s=new XMLHttpRequest;s.open("GET",e),s.responseType="blob",s.onload=function(){Vo(s.response,t,i)},s.onerror=function(){console.error("could not download file")},s.send()}function zo(e){var t=new XMLHttpRequest;t.open("HEAD",e,!1);try{t.send()}catch(e){}return t.status>=200&&t.status<=299}function $o(e){try{e.dispatchEvent(new MouseEvent("click"))}catch(i){var t=document.createEvent("MouseEvents");t.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),e.dispatchEvent(t)}}var Ho=jo.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),Vo="object"!=typeof window||window!==jo?function(){}:"download"in HTMLAnchorElement.prototype&&!Ho?function(e,t,i){var s=jo.URL||jo.webkitURL,r=document.createElementNS("http://www.w3.org/1999/xhtml","a");t=t||e.name||"download",r.download=t,r.rel="noopener","string"==typeof e?(r.href=e,r.origin!==location.origin?zo(r.href)?Go(e,t,i):$o(r,r.target="_blank"):$o(r)):(r.href=s.createObjectURL(e),setTimeout((function(){s.revokeObjectURL(r.href)}),4e4),setTimeout((function(){$o(r)}),0))}:"msSaveOrOpenBlob"in navigator?function(e,t,i){if(t=t||e.name||"download","string"==typeof e)if(zo(e))Go(e,t,i);else{var s=document.createElement("a");s.href=e,s.target="_blank",setTimeout((function(){$o(s)}))}else navigator.msSaveOrOpenBlob(function(e,t){return void 0===t?t={autoBom:!1}:"object"!=typeof t&&(console.warn("Deprecated: Expected third argument to be a object"),t={autoBom:!t}),t.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e}(e,i),t)}:function(e,t,i,s){if((s=s||open("","_blank"))&&(s.document.title=s.document.body.innerText="downloading..."),"string"==typeof e)return Go(e,t,i);var r="application/octet-stream"===e.type,a=/constructor/i.test(jo.HTMLElement)||jo.safari,o=/CriOS\/[\d]+/.test(navigator.userAgent);if((o||r&&a||Ho)&&"undefined"!=typeof FileReader){var n=new FileReader;n.onloadend=function(){var e=n.result;e=o?e:e.replace(/^data:[^;]*;/,"data:attachment/file;"),s?s.location.href=e:location=e,s=null},n.readAsDataURL(e)}else{var l=jo.URL||jo.webkitURL,d=l.createObjectURL(e);s?s.location=d:location.href=d,s=null,setTimeout((function(){l.revokeObjectURL(d)}),4e4)}};class Wo{constructor(e,t){this.canvas=e,this.gl=t;const i=t.createShader(t.VERTEX_SHADER);if(t.shaderSource(i,"\n attribute vec2 xy;\n varying highp vec2 uv;\n void main(void) {\n gl_Position = vec4(xy, 0.0, 1.0);\n // Map vertex coordinates (-1 to +1) to UV coordinates (0 to 1).\n // UV coordinates are Y-flipped relative to vertex coordinates.\n uv = vec2((1.0 + xy.x) / 2.0, (1.0 - xy.y) / 2.0);\n }\n "),t.compileShader(i),!t.getShaderParameter(i,t.COMPILE_STATUS))throw t.getShaderInfoLog(i);const s=t.createShader(t.FRAGMENT_SHADER);if(t.shaderSource(s,"\n varying highp vec2 uv;\n uniform sampler2D texture;\n void main(void) {\n gl_FragColor = texture2D(texture, uv);\n }\n "),t.compileShader(s),!t.getShaderParameter(s,t.COMPILE_STATUS))throw t.getShaderInfoLog(s);const r=t.createProgram();if(t.attachShader(r,i),t.attachShader(r,s),t.linkProgram(r),!t.getProgramParameter(r,t.LINK_STATUS))throw t.getProgramInfoLog(r);t.useProgram(r);const a=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,a),t.bufferData(t.ARRAY_BUFFER,new Float32Array([-1,-1,-1,1,1,1,1,-1]),t.STATIC_DRAW);const o=t.getAttribLocation(r,"xy");t.vertexAttribPointer(o,2,t.FLOAT,!1,0,0),t.enableVertexAttribArray(o);const n=t.createTexture();t.bindTexture(t.TEXTURE_2D,n),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),this.program=r,this.buffer=a,this.vertexShader=i,this.fragmentShader=s,this.texture=n}destroy(){this.gl.deleteProgram(this.program),this.gl.deleteBuffer(this.buffer),this.gl.deleteTexture(this.texture),this.gl.deleteShader(this.vertexShader),this.gl.deleteShader(this.fragmentShader),this.program=null,this.buffer=null,this.vertexShader=null,this.fragmentShader=null,this.texture=null}render(e){this.canvas.width=e.displayWidth,this.canvas.height=e.displayHeight;const t=this.gl;t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),t.viewport(0,0,t.drawingBufferWidth,t.drawingBufferHeight),t.clearColor(1,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.drawArrays(t.TRIANGLE_FAN,0,4)}}class Jo extends No{constructor(e){super(),this.player=e;const t=document.createElement("canvas");t.style.position="absolute",t.style.top=0,t.style.left=0,this.$videoElement=t,e.$container.appendChild(this.$videoElement),this.context2D=null,this.contextGl=null,this.webglRender=null,this.webglRectRender=null,this.webGPURender=null,this.isWebglContextLost=!1,this.isWcsWebgl2=!1,this.bitmaprenderer=null,this.renderType=null,this.controlHeight=0,this.proxyDestroyList=[],this._initCanvasRender()}destroy(){super.destroy(),this.proxyDestroyList.length>0&&(this.proxyDestroyList.forEach((e=>{e&&e()})),this.proxyDestroyList=[]),this.contextGl&&(this.contextGl=null),this.context2D&&(this.context2D=null),this.webglRender&&(this.webglRender.destroy(),this.webglRender=null),this.webglRectRender&&(this.webglRectRender.destroy(),this.webglRectRender=null),this.webGPURender&&(this.webGPURender.destroy(),this.webGPURender=null),this.bitmaprenderer&&(this.bitmaprenderer=null),this.renderType=null,this.isWebglContextLost=!1,this.videoInfo={width:"",height:"",encType:""},this.player.$container.removeChild(this.$videoElement),this.init=!1,this.off()}_initContext2D(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.context2D=this.$videoElement.getContext("2d",e)}_initContextGl(){if(this.player.events,this.contextGl=na(this.$videoElement),!this.contextGl)throw this.player.debug.error("CommonCanvasLoader","_initContextGl() createContextGL error"),new Error("CommonCanvasLoader and _initContextGl createContextGL error");this._bindContextGlEvents(),this.webglRender=new Uo(this.contextGl,this.player._opt.openWebglAlignment)}_initContextGl2(){if(this.contextGl=la(this.$videoElement),this.contextGl){this._bindContextGlEvents(2);try{this.webglRender=new Wo(this.$videoElement,this.contextGl)}catch(e){this.player.debug.error("CommonCanvasLoader",`create webgl2Render error is ${e} and next use context2d.draw render`),this.contextGl=null,this.webglRender=null,this._initContext2D()}}else this.player.debug.error("CommonCanvasLoader","_initContextGl2() createContextGL2 error")}_bindContextGlEvents(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;const{proxy:t}=this.player.events,i=t(this.$videoElement,"webglcontextlost",(t=>{t.preventDefault(),this.player.debug.error("canvasVideo","webglcontextlost error",t),this.isWebglContextLost=!0,this.webglRender&&(this.player.debug.log("CommonCanvasLoader","webglcontextlost error and destroy webglRender"),this.webglRender.destroy(),this.webglRender=null),this.webglRectRender&&(this.player.debug.log("CommonCanvasLoader","webglcontextlost error and destroy webglRectRender"),this.webglRectRender.destroy(),this.webglRectRender=null),this.contextGl=null,setTimeout((()=>{if(this.player.debug.log("CommonCanvasLoader",`createContextGL() version ${e}`),1===e?this.contextGl=na(this.$videoElement):2===e&&(this.contextGl=la(this.$videoElement)),this.player.debug.log("CommonCanvasLoader","createContextGL success"),this.contextGl&&this.contextGl.getContextAttributes){const t=this.contextGl.getContextAttributes();t&&t.stencil?(1===e?this.webglRender=new Uo(this.contextGl,this.player._opt.openWebglAlignment):2===e&&(this.webglRender=new Wo(this.$videoElement,this.contextGl)),this.isWebglContextLost=!1,this.player.debug.log("CommonCanvasLoader","webglcontextlost error reset and getContextAttributes().stencil is true")):(this.player.debug.error("CommonCanvasLoader","webglcontextlost error, getContextAttributes().stencil is false"),this.player.emitError(ut.webglContextLostError))}else this.player.debug.error("CommonCanvasLoader","webglcontextlost error, getContextAttributes().stencil is false"),this.player.emitError(ut.webglContextLostError)}),500)})),s=t(this.$videoElement,"webglcontextrestored",(e=>{e.preventDefault(),this.player.debug.log("CommonCanvasLoader","webglcontextrestored ",e)}));this.proxyDestroyList.push(i,s)}_initContextGPU(){var t;(t=this.$videoElement,new Promise(((e,i)=>{navigator.gpu?navigator.gpu.requestAdapter().then((s=>{s?s.requestDevice().then((r=>{if(r){const a=t.getContext("webgpu");if(a){const t=navigator.gpu.getPreferredCanvasFormat();a.configure({device:r,format:t,alphaMode:"opaque"}),e({adapter:s,device:r,context:a,format:t})}else i('WebGPU "context" create fail')}else i('WebGPU "device" request fail')})).catch((e=>{i('WebGPU "adapter.requestDevice()" fail')})):i('WebGPU "adapter" request fail is empty')})).catch((e=>{i('WebGPU "navigator.gpu.requestAdapter()" fail')})):i("WebGPU not support!!")}))).then((t=>{t?(this.webGPURender=new Fo(t),this.player.debug.log("CommonCanvasLoader","webGPURender init success")):(this.player.debug.warn("CommonCanvasLoader",`createWebGPUContext error is ${e} and next use webgl render`),this.renderType=ii,this._initContextGl())})).catch((e=>{this.player.debug.warn("CommonCanvasLoader",`createWebGPUContext error is ${e} and next use webgl render`),this.renderType=ii,this._initContextGl()}))}initCanvasViewSize(){this.$videoElement.width=this.videoInfo.width,this.$videoElement.height=this.videoInfo.height,this.resize()}screenshot(e,t,i,s){e=e||ha(),s=s||yt.download;let r=.92;!qi[t]&&yt[t]&&(s=t,t="png",i=void 0),"string"==typeof i&&(s=i,i=void 0),void 0!==i&&(r=Number(i));const a=qi[t]||qi.png,o=this.$videoElement.toDataURL(a,r);if(s===yt.base64)return o;{const t=da(o);if(s===yt.blob)return t;if(s===yt.download){const i=a.split("/")[1];Vo(t,e+"."+i)}}}screenshotWatermark(e){return new Promise(((t,i)=>{Ya(e)&&(e={filename:e}),(e=e||{}).width=this.videoInfo.width,e.height=this.videoInfo.height,e.filename=e.filename||ha(),e.format=e.format?qi[e.format]:qi.png,e.quality=Number(e.quality)||.92,e.type=e.type||yt.download;const s=this.$videoElement.toDataURL(e.format,e.quality);Ga(s,e).then((i=>{if(e.type===yt.base64)t(s);else{const s=da(i);if(e.type===yt.blob)t(s);else if(e.type===yt.download){t();const i=e.format.split("/")[1];Vo(s,e.filename+"."+i)}}})).catch((e=>{i(e)}))}))}render(){}clearView(){super.clearView()}play(){}pause(){}_resize(){this.player.debug.log("canvasVideo","_resize()");const e=this.player._opt;let t=this.player.width,i=this.player.height;if(e.hasControl&&!e.controlAutoHide){const s=this.controlHeight;ya()&&this.player.fullscreen&&e.useWebFullScreen?t-=s:i-=s}let s=this.$videoElement.width,r=this.$videoElement.height;const a=e.rotate;let o=(t-s)/2,n=(i-r)/2;270!==a&&90!==a||(s=this.$videoElement.height,r=this.$videoElement.width);const l=t/s,d=i/r;let h=l>d?d:l;Ao(e.isResize)&&l!==d&&(h=l+","+d),e.isFullResize&&(h=l>d?l:d);let c="scale("+h+")";"none"===e.mirrorRotate&&a&&(c+=" rotate("+a+"deg)"),"level"===e.mirrorRotate?c+=" rotateY(180deg)":"vertical"===e.mirrorRotate&&(c+=" rotateX(180deg)"),this.$videoElement.style.height=this.videoInfo.height+"px",this.$videoElement.style.width=this.videoInfo.width+"px",this.$videoElement.style.padding="0",this.$videoElement.style.transform=c,this.$videoElement.style.left=o+"px",this.$videoElement.style.top=n+"px"}initFps(){}setStreamFps(e){}getStreamFps(){return 25}getType(){return W}getCanvasType(){let e=this.renderType===ri?ri:ii;return this.isWcsWebgl2&&(e=si),e}}class qo extends Jo{constructor(e){super(e),this.yuvList=[],this.controlHeight=Yt,this.tempTextCanvas=null,this.tempTextCanvasCtx=null,this.player.debug.log("CanvasVideo","init")}async destroy(){super.destroy(),this.yuvList=[],this.tempTextCanvas&&(this.tempTextCanvasCtx.clearRect(0,0,this.tempTextCanvas.width,this.tempTextCanvas.height),this.tempTextCanvas.width=0,this.tempTextCanvas.height=0,this.tempTextCanvas=null),this.player.debug.log("CanvasVideoLoader","destroy")}_initCanvasRender(){this.player._opt.useWCS&&!this._supportOffscreen()?(this.renderType=ti,To()&&this.player._opt.wcsUseWebgl2Render?(this._initContextGl2(),this.webglRender&&(this.isWcsWebgl2=!0)):this._initContext2D()):this.player._opt.useMSE&&this.player._opt.mseUseCanvasRender?(this.renderType=oi,this._initContext2D()):this.player.isOldHls()&&this.player._opt.useCanvasRender?(this.renderType=ni,this._initContext2D()):this.player.isWebrtcH264()&&this.player._opt.webrtcUseCanvasRender?(this.renderType=li,this._initContext2D()):this._supportOffscreen()?(this.renderType=ai,this._bindOffscreen()):this.player._opt.useWebGPU?(this.renderType=ri,this._initContextGPU()):(this.renderType=ii,this._initContextGl())}_supportOffscreen(){return"function"==typeof this.$videoElement.transferControlToOffscreen&&this.player._opt.useOffscreen}_bindOffscreen(){this.bitmaprenderer=this.$videoElement.getContext("bitmaprenderer")}render(e){this.yuvList.push(e),this.startRender()}startRender(){for(;!(this.yuvList.length<=0);){const e=this.yuvList.shift();this.doRender(e)}}doRender(e){if(this.renderType!==oi){const t={ts:e.ts||0,fps:!0};this.player.updateStats(t)}switch(this.renderType){case ai:this.bitmaprenderer.transferFromImageBitmap(e.buffer);break;case ii:case ri:if(this.isWebglContextLost)return void this.player.debug.warn("CanvasVideoLoader","doRender() and webgl context is lost");let t=e.output;if(this.player.faceDetectActive&&this.player.ai&&this.player.ai.faceDetector){null===this.prevAiFaceDetectTime&&(this.prevAiFaceDetectTime=ha());const i=ha();i-this.prevAiFaceDetectTime>=this.player._opt.aiFaceDetectInterval&&(t=this.player.ai.faceDetector.detect({width:this.$videoElement.width,height:this.$videoElement.height,data:e.output,ts:e.ts||0}),this.prevAiFaceDetectTime=i)}if(this.player.objectDetectActive&&this.player.ai&&this.player.ai.objectDetector){null===this.prevAiObjectDetectTime&&(this.prevAiObjectDetectTime=ha());const i=ha();i-this.prevAiObjectDetectTime>=this.player._opt.aiObjectDetectInterval&&(t=this.player.ai.objectDetector.detect({width:this.$videoElement.width,height:this.$videoElement.height,data:e.output,ts:e.ts||0}),this.prevAiObjectDetectTime=i)}if(this.player.occlusionDetectActive&&this.player.ai&&this.player.ai.occlusionDetector){null===this.prevAiOcclusionDetectTime&&(this.prevAiOcclusionDetectTime=ha());const t=ha();if(t-this.prevAiOcclusionDetectTime>=this.player._opt.aiOcclusionDetectInterval){const i=this.player.ai.occlusionDetector.check({width:this.$videoElement.width,height:this.$videoElement.height,data:e.output});this.prevAiOcclusionDetectTime=t,i&&this.player.emit(lt.aiOcclusionDetectResult,{ts:e.ts||0})}}if(this.player.imageDetectActive&&this.player.ai&&this.player.ai.imageDetector){const t=this.player.ai.imageDetector.check({width:this.$videoElement.width,height:this.$videoElement.height,data:e.output});if(t&&t.data&&(this.player.emit(lt.aiOcclusionDetectResult,{type:t.type,ts:e.ts||0}),this.player._opt.aiImageDetectDrop))return void this.player.debug.log("CanvasVideoLoader",`doRender() and ai image detect result type is ${t.type} and drop`)}if(this.renderType===ri)try{if(!this.webGPURender)return void this.player.debug.warn("CanvasVideoLoader","doRender webgpu render is not init");this.webGPURender.renderYUV(this.$videoElement.width,this.$videoElement.height,t)}catch(e){this.player.debug.error("CanvasVideoLoader",`doRender webgpu render and error: ${e.toString()}`)}else if(this.renderType===ii)try{this.webglRender.renderYUV(this.$videoElement.width,this.$videoElement.height,t)}catch(e){this.player.debug.error("CanvasVideoLoader",`doRender webgl render context is lost ${this.contextGl&&this.contextGl.isContextLost()} and error: ${e.toString()}`)}break;case ti:if(this.webglRender)this.webglRender.render(e.videoFrame),oo(e.videoFrame);else if(this.context2D)if(to(e.videoFrame.createImageBitmap))try{e.videoFrame.createImageBitmap().then((t=>{this.context2D.drawImage(t,0,0,this.$videoElement.width,this.$videoElement.height),oo(e.videoFrame)}))}catch(e){}else this.context2D.drawImage(e.videoFrame,0,0,this.$videoElement.width,this.$videoElement.height),oo(e.videoFrame);else this.player.debug.warn("CanvasVideoLoader","doRender() and webcodecs context is lost");break;case oi:case ni:case li:this.context2D.drawImage(e.$video,0,0,this.$videoElement.width,this.$videoElement.height)}let t=e.ts||0;this.renderType===oi&&(t=parseInt(1e3*e.$video.currentTime,10)+(this.player.mseDecoder.firstRenderTime||0)),this.player.updateCurrentPts(t),this.doAddContentToWatermark(),this.doAddAiContentToWatermark()}clearView(){switch(super.clearView(),this.renderType){case ai:(function(e,t){const i=document.createElement("canvas");i.width=e,i.height=t;const s=window.createImageBitmap(i,0,0,e,t);return i.width=0,i.height=0,s})(this.$videoElement.width,this.$videoElement.height).then((e=>{this.bitmaprenderer.transferFromImageBitmap(e)}));break;case ii:this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT);break;case ri:this.webGPURender.clear();break;case ti:this.contextGl?this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT):this.context2D&&this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height);break;case oi:case ni:case li:this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height)}}_initTempTextCanvas(){this.tempTextCanvas=document.createElement("canvas"),this.tempTextCanvasCtx=this.tempTextCanvas.getContext("2d"),this.tempTextCanvas.width=600,this.tempTextCanvas.height=20}doAddContentToCanvas(){this.tempContentList.length>0&&this.context2D&&function(e){let{ctx:t,list:i}=e;t.save(),(i||[]).forEach((e=>{"text"===e.type?(t.font=`${e.fontSize||12}px Arial`,t.fillStyle=e.color||"green",t.fillText(e.text,e.x,e.y)):"rect"===e.type&&(t.strokeStyle=e.color||"green",t.lineWidth=e.lineWidth||2,t.strokeRect(e.x,e.y,e.width,e.height))})),t.restore()}({ctx:this.context2D,list:this.tempContentList})}doAddContentToWebGlCanvas(){this.tempContentList.length>0&&this.contextGl&&this.webglRectRender&&this.tempContentList.forEach((e=>{const t=e.x,i=e.y;if("rect"===e.type){const r=e.width,a=e.height,o=(s=e.color||"#008000",[parseInt(s.substring(1,3),16)/255,parseInt(s.substring(3,5),16)/255,parseInt(s.substring(5,7),16)/255,1]),n=e.lineWidth||4;if(!r||!a)return;this.webglRectRender.drawBox({x:t,y:i,width:r,height:a,lineColor:o,lineWidth:n,canvasWidth:this.$videoElement.width,canvasHeight:this.$videoElement.height})}else if("text"===e.type){const s=e.text||"";if(!s)return;const r=e.fontSize||20,a=e.color||"#008000";this.tempTextCanvas||this._initTempTextCanvas(),this.tempTextCanvasCtx.clearRect(0,0,this.tempTextCanvas.width,this.tempTextCanvas.height),this.tempTextCanvasCtx.font=`${r}px Arial`,this.tempTextCanvasCtx.fillStyle=a,this.tempTextCanvasCtx.textBaseline="top",this.tempTextCanvasCtx.fillText(s,0,0),this.webglRender.drawDom(this.$videoElement.width,this.$videoElement.height,t,i,this.tempTextCanvas)}var s}))}}class Ko extends No{constructor(e){super(),this.player=e,this.TAG_NAME="Video";const t=document.createElement("video"),i=document.createElement("canvas");t.muted=!0,t.style.position="absolute",t.style.top=0,t.style.left=0,this._delayPlay=!1,e.$container.appendChild(t),this.$videoElement=t,this.$canvasElement=i,this.canvasContext=i.getContext("2d"),this.mediaStream=null,this.vwriter=null,e.canVideoTrackWritter()&&Pa()&&Ba()&&(this.trackGenerator=new MediaStreamTrackGenerator({kind:"video"}),this.mediaStream=new MediaStream([this.trackGenerator]),t.srcObject=this.mediaStream,this.vwriter=this.trackGenerator.writable.getWriter()),this.fixChromeVideoFlashBug(),this.fixMobileAutoFullscreen(),this.resize(),this.eventListenList=[],this.isRenderRetryPlaying=!1,this.isRenderRetryPlayingTimes=0,this.isRetryPlaying=!1,this.isRetryPlayingTimes=0,this.canplayReceived=!1,this.progressProxyDestroy=null,this.checkVideoCanplayTimeout=null;const s=ko();this.supportVideoFrameCallbackHandle=null;const{proxy:r}=this.player.events,a=r(this.$videoElement,"canplay",(()=>{this.player.debug.log("Video","canplay"),this.player.isDestroyedOrClosed()||(this.canplayReceived=!0,this._delayPlay?(this.clearCheckVideoCanplayTimeout(),this._play(),ko()?this.supportVideoFrameCallbackHandle||(this.supportVideoFrameCallbackHandle=this.$videoElement.requestVideoFrameCallback(this.videoFrameCallback.bind(this))):this.player.debug.warn("Video","not support requestVideoFrameCallback and use timeupdate event to update stats")):this.$videoElement.paused&&this.player.isMSEPlaybackRateChangePause&&(this.player.debug.log("Video",`canplay and video is paused and isMSEPlaybackRateChangePause is ${this.player.isMSEPlaybackRateChangePause} so next try to play`),this.player.isMSEPlaybackRateChangePause=!1,this.$videoElement.play()))})),o=r(this.$videoElement,"waiting",(()=>{this.player.debug.log("Video","waiting")})),n=r(this.$videoElement,"loadedmetadata",(()=>{this.player.debug.log("Video","loadedmetadata")})),l=r(this.$videoElement,"timeupdate",(t=>{if(!this.player.isDestroyedOrClosed()){if(Ao(s)){const t=parseInt(1e3*this.getCurrentTime(),10);(e.isWebrtcH264()||this.player.isOldHls()||this.player.isAliyunRtc())&&(this.player.emit(lt.timeUpdate,t),e.handleRender(),e.updateStats({fps:!0,ts:t,dts:t}))}this.player.isMseDecoderUseWorker()&&(this.player.decoderWorker.updateVideoTimestamp(this.getCurrentTime()),this._handleUpdatePlaybackRate())}})),d=r(this.$videoElement,"error",(()=>{let e={};if(this.player.isUseMSE()&&(e=this.player.getMseMineType()),this.player.debug.error("Video","Error Code "+this.$videoElement.error.code+" "+yr[this.$videoElement.error.code]+"; Details: "+this.$videoElement.error.message+"; Video Info: "+JSON.stringify(this.videoInfo)+"; Mse Mine Type: "+e.video+"; "),this.player.isUseMSE()){this.$videoElement.error.code;const e=this.$videoElement.error.message;-1!==e.indexOf(Ar)&&(this.player.isMSEVideoDecoderInitializationFailedNotSupportHevc=!0),-1!==e.indexOf(br)&&(this.player.isMSEAudioDecoderError=!0)}this.player.isHlsCanVideoPlay()})),h=r(this.$videoElement,"stalled",(()=>{this._detectAndFixStuckPlayback(!0)}));if(this.progressProxyDestroy=r(this.$videoElement,"progress",(()=>{this._detectAndFixStuckPlayback()})),this.eventListenList.push(a,o,l,d,n,h),this.player.isUseMSE()){const e=r(this.$videoElement,rs,(()=>{this.player.debug.log(this.TAG_NAME,"video playback Rate change",this.$videoElement&&this.$videoElement.playbackRate),this.$videoElement&&this.$videoElement.paused&&(this.player.debug.warn(this.TAG_NAME,"ratechange and video is paused and sent isMSEPlaybackRateChangePause true"),this.player.isMSEPlaybackRateChangePause=!0)}));this.eventListenList.push(e),this.player.on(lt.visibilityChange,(e=>{e&&setTimeout((()=>{if(this.player.isPlaying()&&this.$videoElement){const e=this.getVideoBufferLastTime();e-this.$videoElement.currentTime>this.getBufferMaxDelayTime()&&(this.player.debug.log(this.TAG_NAME,`visibilityChange is true and lastTime is ${e} and currentTime is ${this.$videoElement.currentTime} so set currentTime to lastTime`),this.$videoElement.currentTime=e)}}),300)}))}this.player.debug.log("Video","init")}async destroy(){if(super.destroy(),this.clearCheckVideoCanplayTimeout(),this._cancelVideoFrameCallback(),this._removeProgressProxyDestroy(),this.eventListenList.length&&(this.eventListenList.forEach((e=>{e()})),this.eventListenList=[]),this.isRenderRetryPlaying=!1,this.isRenderRetryPlayingTimes=0,this.isRetryPlaying=!1,this.isRetryPlayingTimes=0,this.canplayReceived=!1,this.player._opt.videoRenderSupportScale&&this._isNeedAddBackDropFilter()){const e=this.player.$container;e.style.backdropFilter="none",e.style.transform="none"}if(this.$canvasElement.height=0,this.$canvasElement.width=0,this.$canvasElement=null,this.canvasContext=null,this.$videoElement){this.$videoElement.pause(),this.$videoElement.currentTime=0,this.$videoElement.srcObject&&(this.$videoElement.srcObject=null,this.$videoElement.removeAttribute("srcObject")),this.$videoElement.src&&(this.$videoElement.src="",this.$videoElement.removeAttribute("src"));try{this.$videoElement.load()}catch(e){}this.player.$container.removeChild(this.$videoElement),this.$videoElement=null}this.trackGenerator&&(this.trackGenerator.stop(),this.trackGenerator=null),this.vwriter&&(await this.vwriter.close(),this.vwriter=null),this._delayPlay=!1,this.mediaStream&&(this.mediaStream.getTracks().forEach((e=>e.stop())),this.mediaStream=null),this.off(),this.player.debug.log("Video","destroy")}videoFrameCallback(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(this.player.isDestroyedOrClosed())return void this.player.debug.log("Video","videoFrameCallback() and isDestroyedOrClosed and return");this.player.handleRender();const i=parseInt(1e3*Math.max(t.mediaTime,this.getCurrentTime()),10)||0;if(this.player.isUseHls265UseMse())this.player.updateStats({fps:!0,ts:i});else if(this.player.isMseDecoderUseWorker()){this.player._times.videoStart||(this.player._times.videoStart=ha(),this.player.handlePlayToRenderTimes());const e=i+(this.player._mseWorkerData.firstRenderTime||0);if(this.player.updateStats({fps:!0,dfps:!0,ts:e,mseTs:i}),this.player.emit(lt.timeUpdate,e),Ao(this.getHasInit())&&t.width&&t.height){const e={width:t.width,height:t.height};this.updateVideoInfo(e),this.initCanvasViewSize()}}if(this.player.isWebrtcH264()||this.player.isOldHls()||this.player.isAliyunRtc()){if(this.player.emit(lt.timeUpdate,i),Ao(this.getHasInit())&&t.width&&t.height){const e={width:t.width,height:t.height};this.videoInfo.encTypeCode||this.player.isOldHls()||(e.encTypeCode=vt),this.updateVideoInfo(e)}this.player.updateStats({fps:!0,ts:i,dts:i}),this.player.updateCurrentPts(i),this.doAddContentToWatermark()}else if(yo(this.player._opt.useMSE)&&Ao(this.player._opt.mseUseCanvasRender)){if(this.player.mseDecoder){let e=parseInt(1e3*Math.max(t.mediaTime,this.getCurrentTime()),10)+(this.player.mseDecoder.firstRenderTime||0);this.player.updateCurrentPts(e)}else if(this.player._opt.mseDecoderUseWorker){let e=parseInt(1e3*Math.max(t.mediaTime,this.getCurrentTime()),10)+(this.player._mseWorkerData.firstRenderTime||0);this.player.updateCurrentPts(e)}this.doAddContentToWatermark()}this.supportVideoFrameCallbackHandle=this.$videoElement.requestVideoFrameCallback(this.videoFrameCallback.bind(this))}fixChromeVideoFlashBug(){if(this.player._opt.videoRenderSupportScale&&this._isNeedAddBackDropFilter()){const e=this.player.$container;e.style.backdropFilter="blur(0px)",e.style.transform="translateZ(0)"}}fixMobileAutoFullscreen(){const e=wa(),t=ba();(e||t)&&(this.player.debug.log("Video",`fixMobileAutoFullscreen and isIOS ${e} and isAndroid ${t}`),this.$videoElement.setAttribute("webkit-playsinline","true"),this.$videoElement.setAttribute("playsinline","true"),this.$videoElement.setAttribute("x5-video-player-type","h5-page"))}_detectAndFixStuckPlayback(e){const t=this.$videoElement,i=t.buffered,s=t.readyState;this.player.debug.log(this.TAG_NAME,`_detectAndFixStuckPlayback() and isStalled is ${e} ,canplayReceived is ${this.canplayReceived} ,videoReadyState is ${s}`),e||Ao(this.canplayReceived)||s<2?i.length>0&&t.currentTime{if(this.clearCheckVideoCanplayTimeout(),!this.player.isDestroyedOrClosed()&&Ao(this.isPlaying())){const e=this._getBufferStore();this.player.debug.warn("Video",`checkVideoCanplayTimeout and video is not playing and buffer store is ${e} and retry play`),this.$videoElement.currentTime=e,this._replay()}}),1e3)));this._play()}}_play(){this.$videoElement&&this.$videoElement.play().then((()=>{this._delayPlay=!1,this.player.debug.log("Video","_play success"),this.isPlaying()?(this.player.emit(lt.removeLoadingBgImage),this.isRetryPlayingTimes=0,this.isRetryPlaying=!1):setTimeout((()=>{this._replay()}),100)})).catch((e=>{this.player.isDestroyedOrClosed()?this.player.debug.log("Video","_play error and player is isDestroyedOrClosed and return"):(this.player.debug.error("Video","_play error",e),this.isRetryPlaying=!1,setTimeout((()=>{this._replay()}),100))}))}_replay(){if(!this.isPlaying()&&Ao(this.player.isDestroyedOrClosed())&&Ao(this.isRetryPlaying)){if(this.isRetryPlaying=!0,this.isRetryPlayingTimes>=3)return void(this.player.isWebrtcH264()?(this.player.debug.error("Video",`_replay(webrtc H264) then but not playing and retry play times is ${this.isRetryPlayingTimes} and emit error`),this.player.emitError(ut.videoElementPlayingFailedForWebrtc)):(this.player.debug.error("Video",`_replay then but not playing and retry play times is ${this.isRetryPlayingTimes} and emit error to use canvas render`),this.player.emitError(ut.videoElementPlayingFailed)));this.player.debug.warn("Video",`_play then but not playing and retry play and isRetryPlayingTimes is ${this.isRetryPlayingTimes}`),this._play(),this.isRetryPlayingTimes++}else this.player.debug.log("Video",`_replay() and isPlaying is ${this.isPlaying()} and isRetryPlaying is ${this.isRetryPlaying} and isDestroyedOrClosed is ${this.player.isDestroyedOrClosed()} and return;`)}pause(e){this.player.debug.log(this.TAG_NAME,"pause and isNow is "+e),this.isPlaying()&&(e?(this.$videoElement&&this.$videoElement.pause(),this._cancelVideoFrameCallback()):setTimeout((()=>{this.$videoElement&&this.$videoElement.pause(),this._cancelVideoFrameCallback()}),100))}clearView(){super.clearView(),this.$videoElement&&(this.$videoElement.pause(),this.$videoElement.currentTime=0,this.$videoElement.src&&(this.$videoElement.src="",this.$videoElement.removeAttribute("src")),this.$videoElement.srcObject&&(this.$videoElement.srcObject=null,this.$videoElement.removeAttribute("srcObject")))}screenshot(e,t,i,s){if(!this._canScreenshot())return this.player.debug.warn("Video",`screenshot failed, video is not ready and stats is ${this._getVideoReadyState()}`),null;e=e||ha(),s=s||yt.download;let r=.92;!qi[t]&&yt[t]&&(s=t,t="png",i=void 0),"string"==typeof i&&(s=i,i=void 0),void 0!==i&&(r=Number(i));const a=this.$videoElement;let o=this.$canvasElement;o.width=a.videoWidth,o.height=a.videoHeight,this.canvasContext.drawImage(a,0,0,o.width,o.height);const n=qi[t]||qi.png,l=o.toDataURL(n,r);if(this.canvasContext.clearRect(0,0,o.width,o.height),o.width=0,o.height=0,s===yt.base64)return l;{const t=da(l);if(s===yt.blob)return t;if(s===yt.download){const i=n.split("/")[1];Vo(t,e+"."+i)}}}screenshotWatermark(e){return new Promise(((t,i)=>{if(Ya(e)&&(e={filename:e}),!this._canScreenshot())return this.player.debug.warn("Video","screenshot failed, video is not ready"),i("screenshot failed, video is not ready");const s=this.$videoElement;(e=e||{}).width=s.videoWidth,e.height=s.videoHeight,e.filename=e.filename||ha(),e.format=e.format?qi[e.format]:qi.png,e.quality=Number(e.quality)||.92,e.type=e.type||yt.download;let r=this.$canvasElement;r.width=s.videoWidth,r.height=s.videoHeight,this.canvasContext.drawImage(s,0,0,r.width,r.height);const a=r.toDataURL(e.format,e.quality);this.canvasContext.clearRect(0,0,r.width,r.height),r.width=0,r.height=0,Ga(a,e).then((i=>{if(e.type===yt.base64)t(a);else{const s=da(i);if(e.type===yt.blob)t(s);else if(e.type===yt.download){t();const i=e.format.split("/")[1];Vo(s,e.filename+"."+i)}}})).catch((e=>{i(e)}))}))}initCanvasViewSize(){this.resize()}clear(){const e=this.$videoElement,t=e.buffered,i=t.length?t.end(t.length-1):0;e.currentTime=i}render(e){if(this.vwriter){if(this.$videoElement.srcObject||(this.$videoElement.srcObject=this.mediaStream),this.isPaused()){const e=this._getVideoReadyState();if(this.player.debug.warn("Video","render() error, video is paused and readyState is "+e),4===e&&Ao(this.isRenderRetryPlaying)){if(this.isRenderRetryPlaying=!0,this.isRenderRetryPlayingTimes>3)return this.player.debug.error("Video","render() error, video is paused and readyState is "+e+", retry times is "+this.isRenderRetryPlayingTimes+", emit error and use canvas render"),void this.player.emitError(ut.videoElementPlayingFailed);this.$videoElement.play().then((()=>{this.isRenderRetryPlayingTimes=0,this.isRenderRetryPlaying=!1,this.player.debug.log("Video","render() video is paused and replay success")})).catch((e=>{this.isRenderRetryPlaying=!1,this.isRenderRetryPlayingTimes++,this.player.debug.warn("Video","render() error, video is paused and replay error ",e)}))}}if(this.player.updateStats({fps:!0,ts:e.ts||0}),e.videoFrame)this.vwriter.write(e.videoFrame),oo(e.videoFrame);else if(e.output){let s=e.output;if(this.player.faceDetectActive&&this.player.ai&&this.player.ai.faceDetector){null===this.prevAiFaceDetectTime&&(this.prevAiFaceDetectTime=ha());const t=ha();t-this.prevAiFaceDetectTime>this.player._opt.aiFaceDetectInterval&&(s=this.player.ai.faceDetector.detect({width:this.videoInfo.width,height:this.videoInfo.height,data:e.output,ts:e.ts||0}),this.prevAiFaceDetectTime=t)}if(this.player.objectDetectActive&&this.player.ai&&this.player.ai.objectDetector){null===this.prevAiObjectDetectTime&&(this.prevAiObjectDetectTime=ha());const t=ha();t-this.prevAiObjectDetectTime>this.player._opt.aiObjectDetectInterval&&(s=this.player.ai.objectDetector.detect({width:this.videoInfo.width,height:this.videoInfo.height,data:e.output,ts:e.ts||0}),this.prevAiObjectDetectTime=t)}if(this.player.occlusionDetectActive&&this.player.ai&&this.player.ai.occlusionDetector){null===this.prevAiOcclusionDetectTime&&(this.prevAiOcclusionDetectTime=ha());const t=ha();if(t-this.prevAiOcclusionDetectTime>=this.player._opt.aiOcclusionDetectInterval){const i=this.player.ai.occlusionDetector.check({width:this.videoInfo.width,height:this.videoInfo.height,data:e.output,ts:e.ts||0});this.prevAiOcclusionDetectTime=t,i&&(this.player.debug.log("Video","render() and ai occlusion detect result is true"),this.player.emit(lt.aiOcclusionDetectResult,{ts:e.ts||0}))}}if(this.player.imageDetectActive&&this.player.ai&&this.player.ai.imageDetector){const t=this.player.ai.imageDetector.check({width:this.videoInfo.width,height:this.videoInfo.height,data:e.output,ts:e.ts||0});if(t&&t.data&&(this.player.emit(lt.aiOcclusionDetectResult,{type:t.type,ts:e.ts||0}),this.player._opt.aiImageDetectDrop))return void this.player.debug.log("Video",`render() and ai image detect result type is ${t.type} and drop`)}try{const r=(t=s,i={format:"I420",codedWidth:this.videoInfo.width,codedHeight:this.videoInfo.height,timestamp:e.ts},new VideoFrame(t,i));this.vwriter.write(r),oo(r)}catch(e){this.player.debug.error("Video","render error",e),this.player.emitError(ut.wasmUseVideoRenderError,e)}}this.player.updateCurrentPts(e.ts||0),this.doAddContentToWatermark(),this.doAddAiContentToWatermark()}else this.player.debug.warn("Video","render and this.vwriter is null");var t,i}_resize(){this.player.debug.log("Video","_resize()");let e=this.player.width,t=this.player.height;const i=this.player._opt,s=i.rotate;if(i.hasControl&&!i.controlAutoHide){const s=i.playType===w?Qt:Yt;ya()&&this.player.fullscreen&&i.useWebFullScreen?e-=s:t-=s}this.$videoElement.width=e,this.$videoElement.height=t,this.$videoElement.style.width=e+"px",this.$videoElement.style.height=t+"px",270!==s&&90!==s||(this.$videoElement.width=t,this.$videoElement.height=e,this.$videoElement.style.width=t+"px",this.$videoElement.style.height=e+"px");let r=(e-this.$videoElement.width)/2,a=(t-this.$videoElement.height)/2,o="contain";Ao(i.isResize)&&(o="fill"),i.isFullResize&&(o="none");let n="";"none"===i.mirrorRotate&&s&&(n+=" rotate("+s+"deg)"),"level"===i.mirrorRotate?n+=" rotateY(180deg)":"vertical"===i.mirrorRotate&&(n+=" rotateX(180deg)"),this.player._opt.videoRenderSupportScale&&(this.$videoElement.style.objectFit=o),this.$videoElement.style.transform=n,this.$videoElement.style.padding="0",this.$videoElement.style.left=r+"px",this.$videoElement.style.top=a+"px"}getType(){return J}getCurrentTime(){return this.$videoElement.currentTime}isPlaying(){return this.$videoElement&&Ao(this.$videoElement.paused)&&Ao(this.$videoElement.ended)&&0!==this.$videoElement.playbackRate&&0!==this.$videoElement.readyState}_canScreenshot(){return this.$videoElement&&this.$videoElement.readyState>=1}getPlaybackQuality(){let e=null;if(this.$videoElement){if(to(this.$videoElement.getVideoPlaybackQuality)){const t=this.$videoElement.getVideoPlaybackQuality();e={droppedVideoFrames:t.droppedVideoFrames||t.corruptedVideoFrames,totalVideoFrames:t.totalVideoFrames,creationTime:t.creationTime}}else e={droppedVideoFrames:this.$videoElement.webkitDroppedFrameCount,totalVideoFrames:this.$videoElement.webkitDecodedFrameCount,creationTime:ha()};e&&(e.renderedVideoFrames=e.totalVideoFrames-e.droppedVideoFrames)}return e}setRate(e){this.$videoElement&&(this.$videoElement.playbackRate=e)}get rate(){let e=1;return this.$videoElement&&(e=this.$videoElement.playbackRate),e}clearCheckVideoCanplayTimeout(){this.checkVideoCanplayTimeout&&(clearTimeout(this.checkVideoCanplayTimeout),this.checkVideoCanplayTimeout=null)}_cancelVideoFrameCallback(){this.supportVideoFrameCallbackHandle&&this.$videoElement&&(this.$videoElement.cancelVideoFrameCallback(this.supportVideoFrameCallbackHandle),this.supportVideoFrameCallbackHandle=null)}_getBufferStore(){const e=this.$videoElement;let t=0;return e.buffered.length>0&&(t=e.buffered.start(0)),t}_handleUpdatePlaybackRate(){const e=this.$videoElement,t=e.buffered;t.length&&t.start(0);const i=t.length?t.end(t.length-1):0;let s=e.currentTime;const r=i-s,a=this.getBufferMaxDelayTime();if(this.player.updateStats({mseVideoBufferDelayTime:r}),r>a)this.player.debug.log(this.TAG_NAME,`handleUpdatePlaybackRate and buffered is ${i} and current is ${s} , delay buffer is more than ${a} is ${r} and new time is ${i}`),e.currentTime=i,s=e.currentTime;else if(r<0){if(this.player.debug.warn(this.TAG_NAME,`handleUpdatePlaybackRate and delay buffer is ${i} - current is ${s} = ${r} < 0 and check video is paused : ${e.paused} `),0===i)return void this.player.emit(ut.mediaSourceBufferedIsZeroError,"video.buffered is empty");e.paused&&e.play()}const o=this._getPlaybackRate(i-s);e.playbackRate!==o&&(this.player.debug.log(this.TAG_NAME,`handleUpdatePlaybackRate and buffered is ${i} and current time is ${s} and delay is ${i-s} set playbackRate is ${o} `),e.playbackRate=o)}_getPlaybackRate(e){const t=this.$videoElement;let i=this.player._opt.videoBufferDelay+this.player._opt.videoBuffer;const s=Math.max(i,1e3),r=s/2;return e*=1e3,1===t.playbackRate?e>s?1.2:1:e<=r?1:t.playbackRate}getVideoCurrentTime(){let e=0;return this.$videoElement&&(e=this.$videoElement.currentTime),e}getVideoBufferLastTime(){const e=this.$videoElement;let t=0;if(e){const i=e.buffered;i.length&&i.start(0);t=i.length?i.end(i.length-1):0}return t}getVideoBufferDelayTime(){const e=this.$videoElement;const t=this.getVideoBufferLastTime()-e.currentTime;return t>0?t:0}checkSourceBufferDelay(){const e=this.$videoElement;let t=0,i=0;return e.buffered.length>0&&(i=e.buffered.end(e.buffered.length-1),t=i-e.currentTime),t<0&&(this.player.debug.warn(this.TAG_NAME,`checkVideoSourceBufferDelay ${t} < 0, and buffered is ${i} ,currentTime is ${e.currentTime} , try to seek ${e.currentTime} to ${i}`),e.currentTime=i,t=0),t}checkSourceBufferStore(){const e=this.$videoElement;let t=0;return e.buffered.length>0&&(t=e.currentTime-e.buffered.start(0)),t}getDecodePlaybackRate(){let e=0;const t=this.$videoElement;return t&&(e=t.playbackRate),e}getBufferMaxDelayTime(){let e=(this.player._opt.videoBuffer+this.player._opt.videoBufferDelay)/1e3;return Math.max(5,e+3)}getReadyStateInited(){return this._getVideoReadyState()>=1}}class Yo extends Jo{constructor(e){super(e),this.controlHeight=Qt,this.bufferList=[],this.playing=!1,this.playInterval=null,this.fps=1,this.preFps=1,this.streamFps=0,this.playbackRate=1,this._firstTimestamp=null,this._renderFps=0,this._startfpsTime=null,this._startFpsTimestamp=null,this._hasCalcFps=!1,this.player.on(lt.playbackPause,(e=>{e?(this.pause(),this.player.playback.isPlaybackPauseClearCache&&this.clear()):this.resume()})),this.player.debug.log("CanvasPlaybackLoader","init")}async destroy(){this._stopSync(),this._firstTimestamp=null,this.playing=!1,this.playbackRate=1,this.fps=1,this.preFps=1,this.bufferList=[],this._renderFps=0,this._startfpsTime=null,this._startFpsTimestamp=null,this._hasCalcFps=!1,super.destroy(),this.player.debug.log("CanvasPlaybackLoader","destroy")}_initCanvasRender(){this.player._opt.useWCS?(this.renderType=ti,To()&&this.player._opt.wcsUseWebgl2Render?(this._initContextGl2(),this.webglRender&&(this.isWcsWebgl2=!0)):this._initContext2D()):this.player._opt.useWebGPU?(this.renderType=ri,this._initContextGPU()):(this.renderType=ii,this._initContextGl())}_sync(){this._stopSync(),this._doPlay(),this.playInterval=setInterval((()=>{this._doPlay()}),this.fragDuration)}_doPlay(){if(this.bufferList.length>0&&!this.player.seeking){const e=this.bufferList.shift();e&&e.buffer&&(this._doRender(e.buffer),this.player.handleRender(),this.player.playback.updateStats({ts:e.ts,tfTs:e.tfTs}))}}_stopSync(){this.playInterval&&(clearInterval(this.playInterval),this.playInterval=null)}_doRender(e){if(this.player._opt.useWCS)if(this.webglRender)this.webglRender.render(e),oo(e);else if(to(e.createImageBitmap))try{e.createImageBitmap().then((t=>{this.context2D.drawImage(t,0,0,this.$videoElement.width,this.$videoElement.height),oo(e)}))}catch(e){}else this.context2D.drawImage(e,0,0,this.$videoElement.width,this.$videoElement.height),oo(e);else if(this.getCanvasType()===ii)try{this.webglRender.renderYUV(this.$videoElement.width,this.$videoElement.height,e)}catch(e){this.player.debug.error("CanvasPlaybackLoader",`doRender webgl render context is lost ${this.contextGl&&this.contextGl.isContextLost()} and error: ${e.toString()}`)}else if(this.getCanvasType()===ri)try{if(!this.webGPURender)return void this.player.debug.warn("CanvasVideoLoader","doRender webgpu render is not init");this.webGPURender.renderYUV(this.$videoElement.width,this.$videoElement.height,e)}catch(e){this.player.debug.error("CanvasPlaybackLoader",`doRender webgpu render and error: ${e.toString()}`)}}get rate(){return this.playbackRate}get fragDuration(){return Math.ceil(1e3/(this.fps*this.playbackRate))}get bufferSize(){return this.bufferList.length}getStreamFps(){return this.streamFps}initFps(){this._hasCalcFps?this.player.debug.log("CanvasPlaybackLoader","initFps, has calc fps"):(this.preFps=ca(this.player.playback.fps,1,100),this.fps=this.preFps)}setFps(e){e!==this.fps?(e>100&&this.player.debug.warn("CanvasPlaybackLoader","setFps max",e),e<0&&this.player.debug.warn("CanvasPlaybackLoader","setFps min",e),this.fps=ca(e,1,100),this.player.debug.log("CanvasPlaybackLoader",`setFps ${this.preFps} -> ${this.fps}`),this.player.playback.isUseFpsRender&&this._sync()):this.player.debug.log("CanvasPlaybackLoader",`setFps, same fps ${e}`)}setStreamFps(e){this.player.debug.log("CanvasPlaybackLoader","setStreamFps",e),this._hasCalcFps=!0,this.streamFps=e,this.preFps=e,this.setFps(e)}setRate(e){e!==this.playbackRate&&(this.playbackRate=e,this.player.playback.isUseFpsRender&&this._sync())}render$2(e){null===this._firstTimestamp&&(this._firstTimestamp=e.ts);const t={tfTs:e.ts-this._firstTimestamp,ts:e.ts};e.videoFrame?t.buffer=e.videoFrame:t.buffer=e.output,this.bufferList.push(t),this.startRender(),this.player.handleRender(),this.player.playback.updateStats({ts:e.ts,tfTs:t.tfTs})}startRender(){for(;!(this.bufferList.length<=0);){const e=this.bufferList.shift();this._doRender(e.buffer)}}pushData(e){null===this._firstTimestamp&&(this._firstTimestamp=e.ts);const t={tfTs:e.ts-this._firstTimestamp,ts:e.ts};e.videoFrame?t.buffer=e.videoFrame:t.buffer=e.output;const i=this.player._opt.playbackConfig.isCacheBeforeDecodeForFpsRender;if(i||this.bufferSize>this.fps*this.playbackRate*2&&(this.player.debug.warn("CanvasPlaybackLoader",`buffer size is ${this.bufferSize}`),this._doPlay()),this.bufferList.push(t),!this._hasCalcFps){const e=ho(this.bufferList);null!==e&&e!==this.preFps&&(this.player.debug.log("CanvasPlaybackLoader",`calc fps is ${e} pre fps is ${this.preFps} and updatePreFps`),this.setStreamFps(e))}if(!i){const e=this.bufferList.length,t=e/(this.fps*this.playbackRate);this.player.debug.log("CanvasPlaybackLoader","rate is",t),t<=1?this.setFps(this.preFps):(this.setFps(this.fps+Math.floor(t*this.playbackRate)),this.player.debug.warn("CanvasPlaybackLoader","rate is",t,"fps is",this.fps,"bufferListLength is",e))}}initVideo(){this.player.playback&&this.player.playback.isUseFpsRender&&this._sync(),this.playing=!0}initVideoDelay(){const e=this.player._opt.playbackDelayTime;e>0?this.delayTimeout=setTimeout((()=>{this.initVideo()}),e):this.initVideo()}clearView(){super.clearView(),this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT)}clear(){this.player._opt.useWCS&&this.bufferList.forEach((e=>{e.buffer&&oo(e.buffer)})),this.bufferList=[]}resume(){this.player.playback.isUseFpsRender&&this._sync(),this.playing=!0}pause(){this.player.playback.isUseFpsRender&&this._stopSync(),this.playing=!1}}class Qo{constructor(e){return new(Qo.getLoaderFactory(e._opt))(e)}static getLoaderFactory(e){return e.useMSE?e.mseUseCanvasRender?qo:Ko:e.isHls&&Ao(e.supportHls265)||e.isWebrtc&&Ao(e.isWebrtcH265)?e.useCanvasRender?qo:Ko:e.isAliyunRtc?Ko:e.useWCS?e.playType===w?Yo:!e.useOffscreen&&e.wcsUseVideoRender?Ko:qo:e.playType===w?Yo:e.wasmUseVideoRender&&!e.useOffscreen?Ko:qo}}class Xo extends a{constructor(e){super(),this.bufferList=[],this.player=e,this.$audio=null,this.scriptNode=null,this.workletProcessorNode=null,this.workletWorkerCloseTimeout=null,this.hasInitScriptNode=!1,this.audioContext=new(window.AudioContext||window.webkitAudioContext)({sampleRate:48e3}),this.gainNode=this.audioContext.createGain();const t=this.audioContext.createBufferSource();t.buffer=this.audioContext.createBuffer(1,1,22050),t.connect(this.audioContext.destination),t.noteOn?t.noteOn(0):t.start(0),this.audioBufferSourceNode=t,this.mediaStreamAudioDestinationNode=this.audioContext.createMediaStreamDestination(),this.gainNode.gain.value=0,this._prevVolume=null,this.playing=!1,this.audioInfo={encTypeCode:"",encType:"",channels:"",sampleRate:"",depth:""},this.init=!1,this.hasAudio=!1,this.audioResumeStateTimeout=null}async destroy(){return this.closeAudio(),this.resetInit(),this.clearAudioResumeStateTimeout(),this.audioContext&&(await this.audioContext.close(),this.audioContext=null),this.gainNode&&(this.gainNode.disconnect(),this.gainNode=null),this.hasAudio=!1,this.playing=!1,this.scriptNode&&(this.scriptNode.disconnect(),this.scriptNode.onaudioprocess=oa,this.scriptNode=null),await this._destroyWorklet(),this.workletProcessorNode&&(this.workletProcessorNode.disconnect(),this.workletProcessorNode.port.onmessage=oa,this.workletProcessorNode=null),this.audioBufferSourceNode&&(this.audioBufferSourceNode.stop(),this.audioBufferSourceNode=null),this.mediaStreamAudioDestinationNode&&(this.mediaStreamAudioDestinationNode.disconnect(),this.mediaStreamAudioDestinationNode=null),this.hasInitScriptNode=!1,this._prevVolume=null,this.off(),!0}_destroyWorklet(){return new Promise(((e,t)=>{this.workletProcessorNode?(this.workletProcessorNode.port.postMessage({type:"destroy"}),this.workletWorkerCloseTimeout=setTimeout((()=>{this.player.debug.log(this.TAG_NAME,"send close and wait 10ms destroy directly"),this.workletWorkerCloseTimeout&&(clearTimeout(this.workletWorkerCloseTimeout),this.workletWorkerCloseTimeout=null),e()}),10)):e()}))}resetInit(){this.audioInfo={encTypeCode:"",encType:"",channels:"",sampleRate:"",depth:""},this.init=!1}getAudioInfo(){return this.audioInfo}updateAudioInfo(e){e.encTypeCode&&(this.audioInfo.encTypeCode=e.encTypeCode,this.audioInfo.encType=xt[e.encTypeCode]),e.channels&&(this.audioInfo.channels=e.channels),e.sampleRate&&(this.audioInfo.sampleRate=e.sampleRate),e.depth&&(this.audioInfo.depth=e.depth),this.audioInfo.sampleRate&&this.audioInfo.channels&&this.audioInfo.encType&&!this.init&&(this.player.emit(lt.audioInfo,this.audioInfo),this.init=!0)}get isPlaying(){return this.playing}get isMute(){return 0===this.gainNode.gain.value}get volume(){return this.gainNode.gain.value}get bufferSize(){return this.bufferList.length}get audioContextState(){let e=null;return this.audioContext&&(e=this.audioContext.state),e}initScriptNode(){}initMobileScriptNode(){}initWorkletScriptNode(){}getEngineType(){return""}mute(e){e?(this.setVolume(0),this.clear()):this.setVolume(this.player.lastVolume||.5)}setVolume(e){e=parseFloat(e).toFixed(2),isNaN(e)||(this.audioEnabled(!0),e=ca(e,0,1),null===this._prevVolume?this.player.emit(lt.mute,0===e):0===this._prevVolume&&e>0?this.player.emit(lt.mute,!1):this._prevVolume>0&&0===e&&this.player.emit(lt.mute,!0),this.gainNode.gain.value=e,this.player.emit(lt.volumechange,this.player.volume),this.player.emit(lt.volume,this.player.volume),this._prevVolume=e)}closeAudio(){this.hasInitScriptNode&&(this.scriptNode&&this.scriptNode.disconnect(this.gainNode),this.workletProcessorNode&&this.workletProcessorNode.disconnect(this.gainNode),this.gainNode&&(this.gainNode.disconnect(this.mediaStreamAudioDestinationNode),this.$audio||this.gainNode.disconnect(this.audioContext.destination))),this.clear()}audioEnabled(e){e?this.isStateSuspended()&&(this.audioContext.resume().then((()=>{this.player.emit(lt.audioResumeState,{state:this.audioContextState,isRunning:this.isStateRunning()})})),this.audioResumeStateTimeout=setTimeout((()=>{this.clearAudioResumeStateTimeout(),this.isStateSuspended()&&this.player.emit(lt.audioResumeState,{state:this.audioContextState,isRunning:this.isStateRunning()})}),1e3)):this.isStateRunning()&&this.audioContext.suspend()}isStateRunning(){return"running"===this.audioContextState}isStateSuspended(){return"suspended"===this.audioContextState}clearAudioResumeStateTimeout(){this.audioResumeStateTimeout&&(clearTimeout(this.audioResumeStateTimeout),this.audioResumeStateTimeout=null)}clear(){this.bufferList=[]}play(e,t){}pause(){this.playing=!1}resume(){this.playing=!0}setRate(e){}getAudioBufferSize(){return 0}}class Zo{constructor(e,t,i,s){this.player=e,this.audio=t,this.channel=i,this.bufferSize=s}destroy(){this.buffer=null,this.channel=null}extract(e,t){let i=this.provide(t);for(let t=0;t=o){try{for(let e=0;e2&&void 0!==arguments[2]?arguments[2]:0;const s=2*(t=t||0);i>=0||(i=(e.length-s)/2);const r=2*i;this.ensureCapacity(i+this._frameCount);const a=this.endIndex;this.vector.set(e.subarray(s,s+r),a),this._frameCount+=i}putBuffer(e,t){let i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;t=t||0,i>=0||(i=e.frameCount-t),this.putSamples(e.vector,e.position+t,i)}receive(e){e>=0&&!(e>this._frameCount)||(e=this.frameCount),this._frameCount-=e,this._position+=e}receiveSamples(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;const i=2*t,s=this.startIndex;e.set(this._vector.subarray(s,s+i)),this.receive(t)}extract(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;const s=this.startIndex+2*t,r=2*i;e.set(this._vector.subarray(s,s+r))}ensureCapacity(){const e=parseInt(2*(arguments.length>0&&void 0!==arguments[0]?arguments[0]:0));if(this._vector.length