diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..eef220eb1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +docker/volumes \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..5e41eeb5f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,75 @@ +name: release-ubuntu + +on: + push: + tags: + - "v*.*.*" # 触发条件是推送标签 如git tag v2.7.4 git push origin v2.7.4 + +jobs: + build-ubuntu: + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64] + max-parallel: 1 # 最大并行数 + steps: + - name: Checkout + uses: actions/checkout@v4 # github action运行环境 + + - name: Create release # 创建文件夹 + run: | + rm -rf release + mkdir release + echo ${{ github.sha }} > Release.txt + cp Release.txt LICENSE release/ + cat Release.txt + + - name: Set up JDK 1.8 + uses: actions/setup-java@v4 + with: + # Eclipse基金会维护的开源Java发行版 因为github action参考java的用这个 所以用这个 + # 还有microsoft(微软维护的openjdk发行版) oracle(商用SDK)等 + distribution: 'temurin' + java-version: '8' + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' # Node.js版本 20系列的最新稳定版 + + - name: Compile backend + run: | + mvn package + mvn package -P war + + - name: Compile frontend + run: | + cd ./web + npm install + npm run build:prod + cd ../ + + - name: Package Files + run: | + cp -r ./src/main/resources/static release/ # 复制前端文件 + cp ./target/*.jar release/ # 复制 JAR 文件 + cp ./src/main/resources/application-dev.yml release/application.yml + + BRANCH=${{ github.event.base_ref }} + BRANCH_NAME=$(echo "$BRANCH" | grep -oP 'refs/heads/\K.*') + echo "BRANCH_NAME= ${BRANCH_NAME}" + # 如果无法获取,使用默认分支 + if [[ -z "BRANCH_NAME" ]]; then + BRANCH_NAME="${{ github.event.repository.default_branch }}" + fi + + TAG_NAME="${GITHUB_REF#refs/tags/}" + ZIP_FILE_NAME="${BRANCH_NAME}-${TAG_NAME}.zip" + zip -r "$ZIP_FILE_NAME" release + echo "ZIP_FILE_NAME=$ZIP_FILE_NAME" >> $GITHUB_ENV + + - name: Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ${{ env.ZIP_FILE_NAME }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 776ebe10e..c937a5c05 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ hs_err_pid* /src/main/resources/static/ certificates +/.vs +/docker/volumes diff --git a/README.md b/README.md index c8b5bca57..4f1369a71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![logo](doc/_media/logo.png) -# 开箱即用的28181协议视频平台 +# 开箱即用的国标28181和部标808+1078协议视频平台 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit) [![license](http://img.shields.io/badge/license-MIT-green.svg)](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE) @@ -8,7 +8,7 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls) -WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。 +WEB VIDEO PLATFORM是一个基于GB28181-2016、部标808、部标1078标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。 流媒体服务基于@夏楚 ZLMediaKit [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) 播放器使用@dexter jessibuca [https://github.com/langhuihui/jessibuca/tree/v3](https://github.com/langhuihui/jessibuca/tree/v3) @@ -27,15 +27,6 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网 wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn) ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) -# 付费社群 -[![社群](doc/_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) -> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。 -> 加入三天内不满意可以直接自行推出,星球会直接退款给大家。需要发票可以在星球app中直接咨询星球客服获取。 - -> 星球还提供了包括闭源的全功能试用包, 会随时更新。 - -> 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题。如果暂时无法加入,给项目点个星也是极大的鼓励。 - # gitee仓库 https://gitee.com/pan648540858/wvp-GB28181-pro.git @@ -129,13 +120,38 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持Mysql,Postgresql,金仓等数据库 - [X] 支持录制计划, 根据设定的时间对通道进行录制. 暂不支持将录制的内容转发到国标上级 - [X] 支持国标信令集群 +- [X] 新增支持部标808和部标1078,大量新特性不一一列表了。支持作为网关被国标上级调用部标设备 # 闭源内容 -- [X] 支持ONVIF协议,设备检索,支持点播,云台控制,国标级联点播,自动点播等。 -- [X] 支持部标1078+808协议,支持点播,云台控制,录像回放,位置上报,自动点播等。 -- [X] 支持国标28181-2022协议,支持巡航轨迹查询,PTZ精准控制,存储卡格式化,设备软件升级,OSD配置,h265+aac,支持辅码流,录像倒放等。 -- [X] 支持国网B接口协议。支持注册,获取资源,预览, 云台控制,预置位控制等,可免费定制支持语音对讲、录像回放和抓拍图像。 +- [X] 国标增强版: 支持国标28181-2022协议,支持巡航轨迹查询,PTZ精准控制,存储卡格式化,设备软件升级,OSD配置,h265+aac,支持辅码流,录像倒放等。 +- [X] 全功能版: + - [X] 支持开源所有功能 + - [X] ONVIF协议 + - 设备检索 + - 实时图像预览 + - 录像回放、回放倍速控制 + - 云台控制、预置位控制、云台绝对定位、看守位 + - 聚焦控制 + - 设备重启 + - 设备时间设置以及跟系统时间的差值比较 + - 恢复出厂设置 + - 自动获取设备品牌等信息、支持展示DNS信息、支持协议的展示 + - 国标级联点播、自动点播等。 + - [X] 国网B接口协议 + - 设备注册 + - 资源获取 + - 预览 + - 云台控制 + - 预置位控制等, + - 可免费定制支持语音对讲、录像回放和抓拍图像。 + - [X] 支持按权限分配可以使用的通道 + - [X] 支持电子地图。支持展示通道位置,支持在地图上修改通道位置。可扩展接入高德地图API,支持搜索位置,附近设备。 + - [X] 支持表格导出 + - [X] 拉流代理支持按照品牌拼接url。 + - [X] 播放鉴权,更加安全。 + - [X] 此版本后续开发功能支持直接更新提供,无需二次付费。 + - [X] 提供源码不限制部署次数和支持路数。 # 授权协议 @@ -143,6 +159,17 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git # 技术支持 +# 付费社群 + + +> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。 +> 加入三天内不满意可以直接自行推出,星球会直接退款给大家。需要发票可以在星球app中直接咨询星球客服获取。 + +> 星球还提供了包括闭源的全功能试用包, 会随时更新。 + +> 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题。如果暂时无法加入,给项目点个星也是极大的鼓励。 + + [知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:, - [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp) diff --git a/doc/_content/ability/push.md b/doc/_content/ability/push.md index cc568b3c8..8998a4b75 100644 --- a/doc/_content/ability/push.md +++ b/doc/_content/ability/push.md @@ -18,6 +18,9 @@ WVP支持三种图像输入方式,直播,[拉流代理](_content/ability/pro 1. 默认情况下WVP收到推流信息后,列表中出现这条推流信息,如果你需要共享推流信息到其他国标平台,那么你需要编辑/国标通道配置,配置国标编码. 2. WVP也支持推流前导入大量通道直接推送给上级,点击“下载模板”按钮,根据示例修改模板后,点击“通道导入”按钮导入通道数据. +## 生成推流地址 +可以在推流列表里点击‘生成推流地址’按钮,得到新地址后直接复制到推流设备。 + ## 推拉流鉴权规则 为了保护服务器的WVP默认开启推流鉴权(目前不支持关闭此功能) diff --git a/docker/.env b/docker/.env new file mode 100644 index 000000000..0de472237 --- /dev/null +++ b/docker/.env @@ -0,0 +1,19 @@ +MediaRtmp=10001 +MediaRtsp=10002 +MediaRtp=10003 + +WebHttp=8080 +WebHttps=8081 + +Stream_IP=127.0.0.1 +SDP_IP=127.0.0.1 + +SIP_ShowIP=127.0.0.1 +SIP_Port=8160 +SIP_Domain=3502000000 +SIP_Id=35020000002000000001 +SIP_Password=wvp_sip_password + + +RecordSip=true +RecordPushLive= \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..a7d0bcd3f --- /dev/null +++ b/docker/README.md @@ -0,0 +1,10 @@ +可以在当前目录下: +使用`docker compose up -d`直接运行。 +使用`docker compose up -d -build -force-recreate`强制重新构建所有服务的镜像并删除旧容器重新运行 + +`.env`用来配置环境变量,在这里配好之后,其它的配置会自动联动的。 + +`build.sh`用来以日期为tag构建镜像,推送到指定的容器注册表内(Windows下可以使用`Git Bash`运行) + + +其它的文件的作用暂不明确 \ No newline at end of file diff --git a/docker/build.sh b/docker/build.sh index 5b1016fc3..5dd3e8e53 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -1,45 +1,79 @@ -#/bin/bash -set -e +#!/bin/bash -version=2.7.3 +# 获取当前日期作为标签(格式:YYYYMMDD) +date_tag=$(date +%Y%m%d) -git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git -cd wvp-GB28181-pro/web_src && \ - npm install && \ - npm run build +# 切换到脚本所在目录的上一级目录作为工作目录 +cd "$(dirname "$0")/.." || { + echo "错误:无法切换到上级目录" + exit 1 +} +echo "已切换工作目录到:$(pwd)" + +# 检查私有仓库环境变量 +if [ -z "$DOCKER_REGISTRY" ]; then + echo "未设置DOCKER_REGISTRY环境变量" + read -p "请输入私有Docker注册库地址(如不推送请留空): " input_registry + docker_registry="$input_registry" +else + docker_registry="$DOCKER_REGISTRY" +fi + +# 定义要构建的镜像和对应的Dockerfile路径(相对当前工作目录) +images=( + "wvp-service:docker/wvp/Dockerfile" + "wvp-nginx:docker/nginx/Dockerfile" +) + +# 构建镜像的函数 +build_image() { + local image_name="$1" + local dockerfile_path="$2" -cd ../../ -mkdir -p ./nginx/dist -cp -r wvp-GB28181-pro/src/main/resources/static/* ./nginx/dist + # 检查Dockerfile是否存在 + if [ ! -f "$dockerfile_path" ]; then + echo "错误:未找到Dockerfile - \"$dockerfile_path\",跳过构建" + return 1 + fi + + # 构建镜像 + local full_image_name="${image_name}:${date_tag}" + echo + echo "==============================================" + echo "开始构建镜像:${full_image_name}" + echo "Dockerfile路径:${dockerfile_path}" + + docker build -t "${full_image_name}" -f "${dockerfile_path}" . + if [ $? -ne 0 ]; then + echo "镜像${full_image_name}构建失败" + return 1 + fi + + # 推送镜像(如果设置了仓库地址) + if [ -n "$docker_registry" ]; then + local registry_image="${docker_registry}/${full_image_name}" + echo "给镜像打标签:${registry_image}" + docker tag "${full_image_name}" "${registry_image}" + + echo "推送镜像到注册库" + docker push "${registry_image}" + if [ $? -eq 0 ]; then + echo "镜像${registry_image}推送成功" + else + echo "镜像${registry_image}推送失败" + fi + else + echo "未提供注册库地址,不执行推送" + fi + echo "==============================================" + echo +} -echo "构建ZLM容器" -cd ./media/ -chmod +x ./build.sh -./build.sh -cd ../ +# 循环构建所有镜像 +for item in "${images[@]}"; do + IFS=':' read -r image_name dockerfile_path <<< "$item" + build_image "$image_name" "$dockerfile_path" +done -echo "构建数据库容器" -cd ./mysql/ -chmod +x ./build.sh -./build.sh -cd ../ - -echo "构建Redis容器" -cd ./redis/ -chmod +x ./build.sh -./build.sh -cd ../ - -echo "构建WVP容器" -cd ./wvp/ -chmod +x ./build.sh -./build.sh -cd ../ - -echo "构建Nginx容器" -cd ./nginx/ -chmod +x ./build.sh -./build.sh -cd ../ - -./push.sh +echo "所有镜像处理完成" +exit 0 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8ad454822..9758161c0 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: polaris-redis: - image: polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-redis:latest + image: redis:latest # 使用官方Redis镜像 restart: unless-stopped healthcheck: test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] @@ -11,8 +11,8 @@ services: start_period: 10s networks: - media-net - ports: - - 6379:6379 + # ports: + # - 6379:6379 volumes: - ./redis/conf/redis.conf:/opt/polaris/redis/redis.conf - ./volumes/redis/data/:/data @@ -21,7 +21,7 @@ services: command: redis-server /opt/polaris/redis/redis.conf --appendonly yes polaris-mysql: - image: polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-mysql:latest + image: mysql:8 # 使用官方MySQL 8镜像 restart: unless-stopped healthcheck: test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ] @@ -34,18 +34,18 @@ services: environment: MYSQL_DATABASE: wvp MYSQL_ROOT_PASSWORD: root - MYSQL_USER: root - MYSQL_PASSWORD: root + MYSQL_USER: wvp_user + MYSQL_PASSWORD: wvp_password TZ: Asia/Shanghai - ports: - - 3306:3306 + # ports: + # - 3306:3306 volumes: - ./mysql/conf:/etc/mysql/conf.d - ./logs/mysql:/logs - ./volumes/mysql/data:/var/lib/mysql + - ../数据库/2.7.4/初始化-mysql-2.7.4.sql:/docker-entrypoint-initdb.d/init.sql # 初始化SQL脚本目录 command: [ - 'mysqld', - '--default-authentication-plugin=mysql_native_password', + # '--default-authentication-plugin=mysql_native_password', '--innodb-buffer-pool-size=80M', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_general_ci', @@ -54,69 +54,99 @@ services: ] polaris-media: - image: polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-media:latest + image: zlmediakit/zlmediakit:master # 替换为官方镜像 restart: always networks: - media-net ports: - - "10935:10935" - - "5540:5540" - - "6080:6080" + #- "6080:80/tcp" # [播流]HTTP 安全考虑-非测试阶段需要注释掉,改为由nginx代理播流地址 + #- "4443:443/tcp" # [播流]HTTPS 安全考虑-非测试阶段需要注释掉,改为由nginx代理播流地址 + - "${MediaRtmp:-10935}:${MediaRtmp:-10935}/tcp" # [收流]RTMP + - "${MediaRtmp:-10935}:${MediaRtmp:-10935}/udp" # [收流]RTMP + #- "41935:41935/tcp" # [收流]RTMPS 无效 + - "${MediaRtsp:-5540}:${MediaRtsp:-5540}/tcp" # [收流]RTSP + - "${MediaRtsp:-5540}:${MediaRtsp:-5540}/udp" # [收流]RTSP + #- "45540:45540/tcp" # [收流]RTSPS 无效 + - "${MediaRtp:-10000}:${MediaRtp:-10000}/tcp" # [收流]RTP + - "${MediaRtp:-10000}:${MediaRtp:-10000}/udp" # [收流]RTP volumes: - - ./volumes/video:/opt/media/www/record/ + - ./volumes/video:/opt/media/bin/www/record/ - ./logs/media:/opt/media/log/ + - ./media/config.ini:/conf/config.ini + command: [ + 'MediaServer', + '-c', '/conf/config.ini', + '-l', '0' + ] polaris-wvp: - image: polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-wvp:latest + # 显式指定构建上下文和Dockerfile路径 + build: + context: .. # 构建上下文的根路径 + dockerfile: ./docker/wvp/Dockerfile # 相对于上下文路径的Dockerfile位置 restart: always networks: - media-net ports: - "18978:18978" - - "8116:8116/udp" - - "8116:8116/tcp" + - "${SIP_Port:-8116}:${SIP_Port:-8116}/udp" + - "${SIP_Port:-8116}:${SIP_Port:-8116}/tcp" depends_on: - polaris-redis - polaris-mysql - polaris-media - links: - - polaris-redis - - polaris-mysql - - polaris-media volumes: - - ./wvp/wvp/:/opt/wvp/wvp/ + - ./wvp/wvp/:/opt/ylcx/wvp/ - ./logs/wvp:/opt/wvp/logs/ environment: TZ: "Asia/Shanghai" - # 本机的IP - SIP_HOST: 127.0.0.1 - STREAM_HOST: 127.0.0.1 + # 流链接的IP + Stream_IP: ${Stream_IP} + # SDP里的IP + SDP_IP: ${SDP_IP} + # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 + ZLM_HOOK_HOST: polaris-wvp ZLM_HOST: polaris-media - ZLM_PORT: 6080 ZLM_SERCERT: su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf + + MediaHttp: ${WebHttp:-8080} + #MediaHttps: ${WebHttps:-8081} + MediaRtmp: ${MediaRtmp:-10935} + MediaRtsp: ${MediaRtsp:-5540} + MediaRtp: ${MediaRtp:-10000} + REDIS_HOST: polaris-redis REDIS_PORT: 6379 + DATABASE_HOST: polaris-mysql DATABASE_PORT: 3306 - DATABASE_USER: wvp - DATABASE_PASSWORD: wvp - # 前端跨域配置,nginx容器所在物理机IP - NGINX_HOST: http://127.0.0.1:8080 + DATABASE_USER: wvp_user + DATABASE_PASSWORD: wvp_password + + SIP_ShowIP: ${SIP_ShowIP} + SIP_Port: ${SIP_Port:-8116} + SIP_Domain: ${SIP_Domain} + SIP_Id: ${SIP_Id} + SIP_Password: ${SIP_Password} + + RecordSip: ${RecordSip} + RecordPushLive: ${RecordPushLive} polaris-nginx: - image: polaris-tian-docker.pkg.coding.net/qt/polaris/polaris-nginx:latest + # 显式指定构建上下文和Dockerfile路径 + build: + context: .. # 构建上下文的根路径 + dockerfile: ./docker/nginx/Dockerfile # 相对于上下文路径的Dockerfile位置 ports: - - "8080:8080" + - "${WebHttp:-8080}:8080" depends_on: - polaris-wvp - links: - - polaris-wvp - environment: - WVP_HOST: polaris-wvp - WVP_PORT: 18978 volumes: - - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/templates/:/etc/nginx/templates - ./logs/nginx:/var/log/nginx + environment: + # 流链接的IP + Stream_IP: ${Stream_IP} networks: - media-net diff --git a/docker/media/config.ini b/docker/media/config.ini index 2098e8d49..cc74281aa 100644 --- a/docker/media/config.ini +++ b/docker/media/config.ini @@ -52,21 +52,21 @@ alive_interval=10.0 enable=1 on_flow_report= on_http_access= -on_play= -on_publish= -on_record_mp4= +on_play=http://polaris-wvp:18978/index/hook/on_play +on_publish=http://polaris-wvp:18978/index/hook/on_publish +on_record_mp4=http://polaris-wvp:18978/index/hook/on_record_mp4 on_record_ts= -on_rtp_server_timeout= +on_rtp_server_timeout=http://polaris-wvp:18978/index/hook/on_rtp_server_timeout on_rtsp_auth= on_rtsp_realm= -on_send_rtp_stopped= +on_send_rtp_stopped=http://polaris-wvp:18978/index/hook/on_send_rtp_stopped on_server_exited= -on_server_keepalive= -on_server_started= +on_server_keepalive=http://polaris-wvp:18978/index/hook/on_server_keepalive +on_server_started=http://polaris-wvp:18978/index/hook/on_server_started on_shell_login= -on_stream_changed= -on_stream_none_reader= -on_stream_not_found= +on_stream_changed=http://polaris-wvp:18978/index/hook/on_stream_changed +on_stream_none_reader=http://polaris-wvp:18978/index/hook/on_stream_none_reader +on_stream_not_found=http://polaris-wvp:18978/index/hook/on_stream_not_found retry=1 retry_delay=3.0 stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 @@ -82,10 +82,10 @@ forwarded_ip_header= keepAliveSecond=30 maxReqSize=40960 notFound=404 Not Found

您访问的资源不存在!


ZLMediaKit(git hash:8ccb4e9/%aI,branch:master,build time:2024-11-07T10:34:19)
-port=6080 +port=80 rootPath=./www sendBufSize=65536 -sslport=4443 +sslport=443 virtualPath= [multicast] @@ -99,7 +99,7 @@ auto_close=0 continue_push_ms=3000 enable_audio=1 enable_fmp4=1 -enable_hls=1 +enable_hls=0 enable_hls_fmp4=0 enable_mp4=0 enable_rtmp=1 @@ -111,7 +111,7 @@ hls_save_path=./www modify_stamp=2 mp4_as_player=0 mp4_max_second=3600 -mp4_save_path=/home +mp4_save_path=/opt/media/bin/www paced_sender_ms=0 rtmp_demand=0 rtsp_demand=0 @@ -119,13 +119,14 @@ ts_demand=0 [record] appName=record -enableFmp4=0 +enableFmp4=1 fastStart=0 fileBufSize=65536 fileRepeat=0 sampleMS=500 [rtc] +bfilter=0 datachannel_echo=0 externIP= maxRtpCacheMS=5000 @@ -150,7 +151,7 @@ directProxy=1 enhanced=0 handshakeSecond=15 keepAliveSecond=15 -port=10935 +port=10001 sslport=0 [rtp] @@ -165,8 +166,9 @@ dumpDir= gop_cache=1 h264_pt=98 h265_pt=99 +merge_frame=1 opus_pt=100 -port=10000 +port=10003 port_range=30000-30500 ps_pt=96 rtp_g711_dur_ms=100 @@ -179,7 +181,7 @@ directProxy=1 handshakeSecond=15 keepAliveSecond=15 lowLatency=0 -port=5540 +port=10002 rtpTransportType=-1 sslport=0 @@ -189,6 +191,7 @@ port=0 [srt] latencyMul=4 +passPhrase= pktBufSize=8192 port=9000 timeoutSec=5 diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index 91403f8e7..5e57aab51 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -1,19 +1,28 @@ +FROM ubuntu:24.04 AS builder + + +RUN apt-get update && \ + apt-get install -y nodejs npm && \ + rm -rf /var/lib/apt/lists/* + +COPY ./web /build +WORKDIR /build + +RUN npm --registry=https://registry.npmmirror.com install +RUN npm run build:prod + +WORKDIR /src/main/resources +RUN ls + +WORKDIR /src/main/resources/static +RUN ls + FROM nginx:alpine -RUN apk add --no-cache bash - ARG TZ=Asia/Shanghai -RUN \ - sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ - apk update && \ - apk add tzdata -RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \ - echo '${TZ}' > /etc/timezone -RUN rm -rf /etc/nginx/conf.d/* -RUN mkdir /opt/dist -COPY ./dist /opt/dist -COPY ./conf/nginx.conf /etc/nginx/conf.d + +COPY --from=builder /src/main/resources/static /opt/dist CMD ["nginx","-g","daemon off;"] diff --git a/docker/nginx/conf/nginx.conf b/docker/nginx/conf/nginx.conf deleted file mode 100644 index ede96ec67..000000000 --- a/docker/nginx/conf/nginx.conf +++ /dev/null @@ -1,55 +0,0 @@ -#user nobody; -worker_processes 1; - -#error_log logs/error.log; -#error_log logs/error.log notice; -#error_log logs/error.log info; - -#pid logs/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include mime.types; - default_type application/octet-stream; - - sendfile on; - #tcp_nopush on; - - #keepalive_timeout 0; - keepalive_timeout 65; - - #gzip on; - - server { - listen 8080; - server_name localhost; - - location / { - root /opt/dist; - index index.html index.htm; - } - location /record_proxy/{ - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://polaris-wvp:18978/; - } - location /api/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://polaris-wvp:18978; - } - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } -} diff --git a/docker/nginx/templates/nginx.conf.template b/docker/nginx/templates/nginx.conf.template new file mode 100644 index 000000000..cf0de139e --- /dev/null +++ b/docker/nginx/templates/nginx.conf.template @@ -0,0 +1,110 @@ +server { + listen 8080; + server_name localhost; + + location / { + root /opt/dist; + index index.html index.htm; + } + location /record_proxy/{ + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://polaris-wvp:18978/; + } + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://polaris-wvp:18978; + + + # 从环境变量获取原始主机地址(x.x.x.x) + set $original_host ${Stream_IP}; + + # 执行字符串替换 + # 将媒体资源文件替换为Nginx输出的相对地址 + sub_filter "http://$original_host/index/api/downloadFile" "mediaserver/api/downloadFile"; + sub_filter "http://$original_host:80/index/api/downloadFile" "mediaserver/api/downloadFile"; + sub_filter "https://$original_host/index/api/downloadFile" "mediaserver/api/downloadFile"; + sub_filter "https://$original_host:443/index/api/downloadFile" "mediaserver/api/downloadFile"; + sub_filter "http://$original_host/mp4_record" "mp4_record"; + sub_filter "http://$original_host:80/mp4_record" "mp4_record"; + sub_filter "https://$original_host/mp4_record" "mp4_record"; + sub_filter "https://$original_host:443/mp4_record" "mp4_record"; + + # 设置为off表示替换所有匹配项,而不仅仅是第一个 + sub_filter_once off; + + # 确保响应被正确处理 + sub_filter_types application/json; # 只对JSON响应进行处理 + } + + # 将mediaserver/record转发到目标地址 + location /mediaserver/api/downloadFile { + # 目标服务器地址 + proxy_pass http://polaris-media:80/index/api/downloadFile; + + # 以下是常用的反向代理设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置,根据需要调整 + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + # 仅允许代理/rtp/开头的路径 + location ^~ /rtp/ { + # 代理到ZLMediakit服务 + proxy_pass http://polaris-media:80; + + # 基础HTTP代理配置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持配置 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时设置,根据实际需求调整 + proxy_connect_timeout 60s; + proxy_read_timeout 3600s; + proxy_send_timeout 60s; + } + + # 仅允许代理/rtp/开头的路径 + location ^~ /mp4_record/ { + # 代理到ZLMediakit服务 + proxy_pass http://polaris-media:80; + + # 基础HTTP代理配置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持配置 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时设置,根据实际需求调整 + proxy_connect_timeout 60s; + proxy_read_timeout 3600s; + proxy_send_timeout 60s; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } +} diff --git a/docker/wvp/Dockerfile b/docker/wvp/Dockerfile index 67286b19f..e66b4cda4 100644 --- a/docker/wvp/Dockerfile +++ b/docker/wvp/Dockerfile @@ -1,64 +1,84 @@ -FROM ubuntu:20.04 AS build -ARG Platfrom=amd64 -ARG JDK_NAME +FROM ringcentral/jdk:11 AS builder EXPOSE 18978/tcp EXPOSE 8116/tcp EXPOSE 8116/udp EXPOSE 8080/tcp -RUN apt-get update && \ - DEBIAN_FRONTEND="noninteractive" \ - apt-get install -y --no-install-recommends \ - wget \ - cmake \ - maven \ - git \ - ca-certificates \ - tzdata \ - curl \ - libpcre3 \ - libpcre3-dev \ - zlib1g-dev \ - openssl \ - libssl-dev \ - gdb && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* +#RUN apt-get update && \ + #DEBIAN_FRONTEND="noninteractive" \ + #apt-get install -y --no-install-recommends \ + #wget \ + #cmake \ + #maven \ + #git \ + #ca-certificates \ + #tzdata \ + #curl \ + #libpcre3 \ + #libpcre3-dev \ + #zlib1g-dev \ + #openssl \ + #libssl-dev \ + #gdb && \ + #apt-get autoremove -y && \ + #apt-get clean -y && \ + #rm -rf /var/lib/apt/lists/* -# install jdk1.8 -RUN mkdir -p /opt/download -WORKDIR /opt/download -RUN if [ "$Platfrom" = "arm64" ]; \ - then \ - wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u411-linux-aarch64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ - tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_411/java/' && \ - rm /opt/download/jdk-8.tar.gz; \ - else \ - wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u202-linux-x64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ - tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_202/java/' && \ - rm /opt/download/jdk-8.tar.gz; \ - fi +## install jdk1.8 +#RUN mkdir -p /opt/download +#WORKDIR /opt/download +#RUN if [ "$Platfrom" = "arm64" ]; \ + #then \ + #wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u411-linux-aarch64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ + #tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_411/java/' && \ + #rm /opt/download/jdk-8.tar.gz; \ + #else \ + #wget https://polaris-tian-generic.pkg.coding.net/qt/autopliot/jdk-8u202-linux-x64.tar.gz?version=latest --no-check-certificate -O jdk-8.tar.gz && \ + #tar -zxvf /opt/download/jdk-8.tar.gz -C /usr/local/ --transform 's/jdk1.8.0_202/java/' && \ + #rm /opt/download/jdk-8.tar.gz; \ + #fi -ENV JAVA_HOME /usr/local/java/ -ENV JRE_HOME ${JAVA_HOME}/jre -ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib -ENV PATH ${JAVA_HOME}/bin:$PATH +#ENV JAVA_HOME /usr/local/java/ +#ENV JRE_HOME ${JAVA_HOME}/jre +#ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib +#ENV PATH ${JAVA_HOME}/bin:$PATH RUN java -version && javac -version +#RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \ +RUN apt-get update && \ + apt-get install -y maven && \ + rm -rf /var/lib/apt/lists/* + + +COPY . /build +WORKDIR /build +RUN ls && mvn clean package -Dmaven.test.skip=true +WORKDIR /build/target +RUN mv wvp-pro-*.jar wvp.jar + + +FROM ringcentral/jdk:11 RUN mkdir -p /opt/wvp WORKDIR /opt/wvp -COPY ./wvp /opt/wvp +COPY --from=builder /build/target /opt/wvp +COPY ./docker/wvp/wvp /opt/wvp +ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/ylcx/", "-jar", "wvp.jar", "--spring.config.location=/opt/ylcx/wvp/application.yml"] -WORKDIR /home -RUN cd /home && \ - git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git -RUN cd /home/wvp-GB28181-pro && \ - mvn clean package -Dmaven.test.skip=true && \ - cp /home/wvp-GB28181-pro/target/*.jar /opt/wvp/wvp.jar -WORKDIR /opt/wvp -ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/ylcx/", "-jar", "wvp.jar", "--spring.config.location=/opt/ylcx/wvp/application.yml"] \ No newline at end of file +#RUN mkdir -p /opt/wvp +#WORKDIR /opt/wvp +#COPY ./wvp /opt/wvp +# +#WORKDIR /home +#RUN cd /home && \ + #git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git +# +#RUN cd /home/wvp-GB28181-pro && \ + #mvn clean package -Dmaven.test.skip=true && \ + #cp /home/wvp-GB28181-pro/target/*.jar /opt/wvp/wvp.jar +# +#WORKDIR /opt/wvp +#ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/ylcx/", "-jar", "wvp.jar", "--spring.config.location=/opt/ylcx/wvp/application.yml"] \ No newline at end of file diff --git a/docker/wvp/wvp/application-docker.yml b/docker/wvp/wvp/application-docker.yml index 68c411e7b..04eeff8df 100644 --- a/docker/wvp/wvp/application-docker.yml +++ b/docker/wvp/wvp/application-docker.yml @@ -1,73 +1,107 @@ spring: - # 设置接口超时时间 - mvc: - async: - request-timeout: 20000 - thymeleaf: - cache: false - # [可选]上传文件大小限制 - servlet: - multipart: - max-file-size: 10MB - max-request-size: 100MB - # REDIS数据库配置 - redis: - # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 - host: ${REDIS_HOST:127.0.0.1} - # [必须修改] 端口号 - port: ${REDIS_PORT:6379} - # [可选] 数据库 DB - database: 1 - # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 - password: - # [可选] 超时时间 - timeout: 30000 - # mysql数据源 - datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${DATABASE_HOST:127.0.0.1}:${DATABASE_PORT:3306}/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true - username: ${DATABASE_USER:root} - password: ${DATABASE_PASSWORD:root} + cache: + type: redis + thymeleaf: + cache: false + # 设置接口超时时间 + mvc: + async: + request-timeout: 20000 + # [可选]上传文件大小限制 + servlet: + multipart: + max-file-size: 10MB + max-request-size: 100MB + # REDIS数据库配置 + redis: + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 + host: ${REDIS_HOST:127.0.0.1} + # [必须修改] 端口号 + port: ${REDIS_PORT:6379} + # [可选] 数据库 DB + database: 1 + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 + password: + # [可选] 超时时间 + timeout: 10000 + ## [可选] 一个pool最多可分配多少个jedis实例 + #poolMaxTotal: 1000 + ## [可选] 一个pool最多有多少个状态为idle(空闲)的jedis实例 + #poolMaxIdle: 500 + ## [可选] 最大的等待时间(秒) + #poolMaxWait: 5 + # [必选] jdbc数据库配置 + datasource: + # mysql数据源 + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${DATABASE_HOST:127.0.0.1}:${DATABASE_PORT:3306}/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true + username: ${DATABASE_USER:root} + password: ${DATABASE_PASSWORD:root} #[可选] 监听的HTTP端口, 网页和接口调用都是这个端口 server: - port: 18978 - ssl: - # [可选] 是否开启HTTPS访问 - enabled: false + port: 18978 + ssl: + # [可选] 是否开启HTTPS访问 + # docker里运行,内部不需要HTTPS + enabled: false # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP - ip: ${SIP_HOST:127.0.0.1} - # [可选] - port: 8116 - # [可选] - domain: 3402000000 - # [可选] - id: 34020000002000000001 - password: - alarm: true + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 + ip: 0.0.0.0 + # [可选] 没有任何业务需求,仅仅是在前端展示的时候用 + show-ip: ${SIP_ShowIP} + # [可选] + port: ${SIP_Port:8116} + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) + # 后两位为行业编码,定义参照附录D.3 + # 3701020049标识山东济南历下区 信息行业接入 + # [可选] + domain: ${SIP_Domain:3402000000} + # [可选] + id: ${SIP_Id:34020000002000000001} + # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 + password: ${SIP_Password} + # [可选] 国标级联注册失败,再次发起注册的时间间隔。 默认60秒 + register-time-interval: 60 + # [可选] 云台控制速度 + ptz-speed: 50 + # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。 + # keepalliveToOnline: false + # 是否存储alarm信息 + alarm: true + # 命令发送等待回复的超时时间, 单位:毫秒 + timeout: 1000 # 默认服务器配置 media: id: polaris # [必须修改] ZLM 内网IP与端口 ip: ${ZLM_HOST:127.0.0.1} - http-port: ${ZLM_PORT:6080} + http-port: 80 # [可选] 返回流地址时的ip,置空使用 media.ip - stream-ip: ${STREAM_HOST:127.0.0.1} + stream-ip: ${Stream_IP} # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip - sdp-ip: ${SIP_HOST:127.0.0.1} - # [可选] Hook IP, 默认使用sip.ip - hook-ip: ${SIP_HOST:127.0.0.1} + sdp-ip: ${SDP_IP} + # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 + hook-ip: ${ZLM_HOOK_HOST} # [可选] sslport - http-ssl-port: 4443 - rtp-proxy-port: 10000 - rtmp-port: 10935 - rtmp-ssl-port: 41935 - rtsp-port: 5540 - rtsp-ssl-port: 45540 + http-ssl-port: 0 + flv-port: ${MediaHttp:} + flv-ssl-port: ${MediaHttps:} + ws-flv-port: ${MediaHttp:} + ws-flv-ssl-port: ${MediaHttps:} + rtp-proxy-port: ${MediaRtp:} + rtmp-port: ${MediaRtmp:} + rtmp-ssl-port: 0 + rtsp-port: ${MediaRtsp:} + rtsp-ssl-port: 0 + # [可选] 是否自动配置ZLM, 如果希望手动配置ZLM, 可以设为false, 不建议新接触的用户修改 + auto-config: true # [可选] secret: ${ZLM_SERCERT} # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 @@ -79,28 +113,28 @@ media: # [可选] send-port-range: 50502,50506 - record-path: /opt/media/record + record-path: /opt/media/bin/www/record/ record-day: 7 record-assist-port: 0 user-settings: auto-apply-play: true play-timeout: 30000 wait-track: false - record-push-live: false - record-sip: false + record-push-live: ${RecordPushLive:false} + record-sip: ${RecordSip:false} stream-on-demand: true - interface-authentication: false + interface-authentication: true broadcast-for-platform: TCP-PASSIVE push-stream-after-ack: true send-to-platforms-when-id-lost: true interface-authentication-excludes: - - /api/** - push-authority: false - allowed-origins: - - http://localhost:8080 - - http://127.0.0.1:8080 - - http://0.0.0.0:8080 - - ${NGINX_HOST} + # - /api/** + push-authority: true + # allowed-origins: + # - http://localhost:8080 + # - http://127.0.0.1:8080 + # - http://0.0.0.0:8080 + # - ${NGINX_HOST} logging: config: classpath:logback-spring.xml diff --git a/pom.xml b/pom.xml index 705f07b0f..b3eb307d6 100644 --- a/pom.xml +++ b/pom.xml @@ -364,6 +364,27 @@ 32.1.3-jre + + + org.apache.ftpserver + ftpserver-core + 1.2.0 + + + + org.apache.ftpserver + ftplet-api + 1.2.0 + + + + + org.projectlombok + lombok + 1.18.30 + provided + + org.projectlombok @@ -383,6 +404,7 @@ log-viewer-spring-boot 1.0.10 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index 28e7d6416..78c93e523 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -79,6 +79,8 @@ public class StreamInfo implements Serializable, Cloneable{ private String startTime; @Schema(description = "结束时间") private String endTime; + @Schema(description = "时长(回放时使用)") + private Double duration; @Schema(description = "进度(录像下载使用)") private double progress; @Schema(description = "文件下载地址(录像下载使用)") @@ -101,87 +103,112 @@ public class StreamInfo implements Serializable, Cloneable{ @Schema(description = "使用的WVP ID") private String serverId; - public void setRtmp(String host, int port, int sslPort, String app, String stream, String callIdParam) { + @Schema(description = "流绑定的流媒体操作key") + private String key; + + public void setRtmp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s%s", app, stream, callIdParam); - if (port > 0) { + if (port != null && port > 0) { this.rtmp = new StreamURL("rtmp", host, port, file); } - if (sslPort > 0) { + if (sslPort != null && sslPort > 0) { this.rtmps = new StreamURL("rtmps", host, sslPort, file); } } - public void setRtsp(String host, int port, int sslPort, String app, String stream, String callIdParam) { + public void setRtsp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s%s", app, stream, callIdParam); - if (port > 0) { + if (port != null && port > 0) { this.rtsp = new StreamURL("rtsp", host, port, file); } - if (sslPort > 0) { + if (sslPort != null && sslPort > 0) { this.rtsps = new StreamURL("rtsps", host, sslPort, file); } } - public void setFlv(String host, int port, int sslPort, String file) { - if (port > 0) { + public void setFlv(String host, Integer port, Integer sslPort, String file) { + if (port != null && port > 0) { this.flv = new StreamURL("http", host, port, file); } - this.ws_flv = new StreamURL("ws", host, port, file); - if (sslPort > 0) { + if (sslPort != null && sslPort > 0) { this.https_flv = new StreamURL("https", host, sslPort, file); - this.wss_flv = new StreamURL("wss", host, sslPort, file); } } - public void setWsFlv(String host, int port, int sslPort, String file) { - if (port > 0) { + public void setWsFlv(String host, Integer port, Integer sslPort, String file) { + if (port != null && port > 0) { this.ws_flv = new StreamURL("ws", host, port, file); } - if (sslPort > 0) { + if (sslPort != null && sslPort > 0) { this.wss_flv = new StreamURL("wss", host, sslPort, file); } } - public void setFmp4(String host, int port, int sslPort, String app, String stream, String callIdParam) { - String file = String.format("%s/%s.live.mp4%s", app, stream, callIdParam); - if (port > 0) { + public void setFmp4(String host, Integer port, Integer sslPort, String file) { + if (port != null && port > 0) { this.fmp4 = new StreamURL("http", host, port, file); + } + if (sslPort != null && sslPort > 0) { + this.https_fmp4 = new StreamURL("https", host, sslPort, file); + } + } + + public void setWsMp4(String host, Integer port, Integer sslPort, String file) { + if (port != null && port > 0) { this.ws_fmp4 = new StreamURL("ws", host, port, file); } - if (sslPort > 0) { - this.https_fmp4 = new StreamURL("https", host, sslPort, file); + if (sslPort != null && sslPort > 0) { this.wss_fmp4 = new StreamURL("wss", host, sslPort, file); } } - public void setHls(String host, int port, int sslPort, String app, String stream, String callIdParam) { + public void setHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam); - if (port > 0) { + if (port != null && port > 0) { this.hls = new StreamURL("http", host, port, file); + } + if (sslPort != null && sslPort > 0) { + this.https_hls = new StreamURL("https", host, sslPort, file); + } + } + + public void setWsHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { + String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam); + if (port != null && port > 0) { this.ws_hls = new StreamURL("ws", host, port, file); } - if (sslPort > 0) { - this.https_hls = new StreamURL("https", host, sslPort, file); + if (sslPort != null && sslPort > 0) { this.wss_hls = new StreamURL("wss", host, sslPort, file); } } - public void setTs(String host, int port, int sslPort, String app, String stream, String callIdParam) { + public void setTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam); - if (port > 0) { + if (port != null && port > 0) { this.ts = new StreamURL("http", host, port, file); + } + if (sslPort != null && sslPort > 0) { + this.https_ts = new StreamURL("https", host, sslPort, file); + } + } + + public void setWsTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) { + String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam); + + if (port != null && port > 0) { this.ws_ts = new StreamURL("ws", host, port, file); } - if (sslPort > 0) { - this.https_ts = new StreamURL("https", host, sslPort, file); + if (sslPort != null && sslPort > 0) { this.wss_ts = new StreamURL("wss", host, sslPort, file); } } - public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) { + public void setRtc(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam, boolean isPlay) { if (callIdParam != null) { callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&"); } +// String file = String.format("%s/%s?type=%s%s", app, stream, isPlay?"play":"push", callIdParam); String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam); if (port > 0) { this.rtc = new StreamURL("http", host, port, file); diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java index 17bd502df..ca0be92e3 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -153,4 +153,15 @@ public class VideoManagerConstants { */ public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:"; + //************************** 1078 **************************************** + + + public static final String INVITE_INFO_1078_POSITION = "INVITE_INFO_1078_POSITION:"; + public static final String INVITE_INFO_1078_PLAY = "INVITE_INFO_1078_PLAY:"; + public static final String INVITE_INFO_1078_PLAYBACK = "INVITE_INFO_1078_PLAYBACK:"; + public static final String INVITE_INFO_1078_TALK = "INVITE_INFO_1078_TALK:"; + + + public static final String RECORD_LIST_1078 = "RECORD_LIST_1078:"; + } diff --git a/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java b/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java index c2d2a11e0..27a09eafc 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java +++ b/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java @@ -4,18 +4,18 @@ package com.genersoft.iot.vmp.common.enums; * 支持的通道数据类型 */ -public enum ChannelDataType { +public class ChannelDataType { - GB28181(1,"国标28181"), - STREAM_PUSH(2,"推流设备"), - STREAM_PROXY(3,"拉流代理"); + public final static int GB28181 = 1; + public final static int STREAM_PUSH = 2; + public final static int STREAM_PROXY = 3; + public final static int JT_1078 = 200; + + public final static String PLAY_SERVICE = "sourceChannelPlayService"; + public final static String PLAYBACK_SERVICE = "sourceChannelPlaybackService"; + public final static String DOWNLOAD_SERVICE = "sourceChannelDownloadService"; + public final static String PTZ_SERVICE = "sourceChannelPTZService"; - public final int value; - public final String desc; - ChannelDataType(Integer value, String desc) { - this.value = value; - this.desc = desc; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java index ef9669d5d..51a586c97 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.utils.DateUtil; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -15,6 +16,7 @@ import java.util.regex.Pattern; @Slf4j @Configuration("mediaConfig") @Order(0) +@Data public class MediaConfig{ // 修改必须配置,不再支持自动获取 @@ -45,6 +47,9 @@ public class MediaConfig{ @Value("${media.flv-port:0}") private Integer flvPort = 0; + @Value("${media.mp4-port:0}") + private Integer mp4Port = 0; + @Value("${media.ws-flv-port:0}") private Integer wsFlvPort = 0; @@ -66,6 +71,9 @@ public class MediaConfig{ @Value("${media.rtp-proxy-port:0}") private Integer rtpProxyPort = 0; + @Value("${media.jtt-proxy-port:0}") + private Integer jttProxyPort = 0; + @Value("${media.rtsp-port:0}") private Integer rtspPort = 0; @@ -99,33 +107,7 @@ public class MediaConfig{ @Value("${media.type:zlm}") private String type; - public String getId() { - return id; - } - public String getIp() { - return ip; - } - - public String getHookIp() { - return hookIp; - } - - public int getHttpPort() { - return httpPort; - } - - public int getHttpSSlPort() { - return httpSSlPort; - } - - public int getRtmpPort() { - return rtmpPort; - } - - public int getRtmpSSlPort() { - return rtmpSSlPort; - } public int getRtpProxyPort() { if (rtpProxyPort == null) { @@ -136,32 +118,12 @@ public class MediaConfig{ } - public int getRtspPort() { - return rtspPort; - } - - public int getRtspSSLPort() { - return rtspSSLPort; - } - - public boolean isAutoConfig() { - return autoConfig; - } - - public String getSecret() { - return secret; - } - - public boolean isRtpEnable() { - return rtpEnable; - } - - public String getRtpPortRange() { - return rtpPortRange; - } - - public int getRecordAssistPort() { - return recordAssistPort; + public Integer getJttProxyPort() { + if (jttProxyPort == null) { + return 0; + }else { + return jttProxyPort; + } } public String getSdpIp() { @@ -191,10 +153,6 @@ public class MediaConfig{ } } - public String getSipDomain() { - return sipDomain; - } - public MediaServer getMediaSerItem(){ MediaServer mediaServer = new MediaServer(); mediaServer.setId(id); @@ -204,31 +162,17 @@ public class MediaConfig{ mediaServer.setSdpIp(getSdpIp()); mediaServer.setStreamIp(getStreamIp()); mediaServer.setHttpPort(httpPort); - if (flvPort == 0) { - mediaServer.setFlvPort(httpPort); - }else { - mediaServer.setFlvPort(flvPort); - } - if (wsFlvPort == 0) { - mediaServer.setWsFlvPort(httpPort); - }else { - mediaServer.setWsFlvPort(wsFlvPort); - } - if (flvSSlPort == 0) { - mediaServer.setFlvSSLPort(httpSSlPort); - }else { - mediaServer.setFlvSSLPort(flvSSlPort); - } - if (wsFlvSSlPort == 0) { - mediaServer.setWsFlvSSLPort(httpSSlPort); - }else { - mediaServer.setWsFlvSSLPort(wsFlvSSlPort); - } + mediaServer.setFlvPort(flvPort); + mediaServer.setMp4Port(mp4Port); + mediaServer.setWsFlvPort(wsFlvPort); + mediaServer.setFlvSSLPort(flvSSlPort); + mediaServer.setWsFlvSSLPort(wsFlvSSlPort); mediaServer.setHttpSSlPort(httpSSlPort); mediaServer.setRtmpPort(rtmpPort); mediaServer.setRtmpSSlPort(rtmpSSlPort); mediaServer.setRtpProxyPort(getRtpProxyPort()); + mediaServer.setJttProxyPort(getJttProxyPort()); mediaServer.setRtspPort(rtspPort); mediaServer.setRtspSSLPort(rtspSSLPort); mediaServer.setAutoConfig(autoConfig); @@ -250,42 +194,10 @@ public class MediaConfig{ return mediaServer; } - public Integer getRecordDay() { - return recordDay; - } - - public void setRecordDay(Integer recordDay) { - this.recordDay = recordDay; - } - - public String getRecordPath() { - return recordPath; - } - - public void setRecordPath(String recordPath) { - this.recordPath = recordPath; - } - - public String getRtpSendPortRange() { - return rtpSendPortRange; - } - - public void setRtpSendPortRange(String rtpSendPortRange) { - this.rtpSendPortRange = rtpSendPortRange; - } - private boolean isValidIPAddress(String ipAddress) { if ((ipAddress != null) && (!ipAddress.isEmpty())) { return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress); } return false; } - - public String getWanIp() { - return wanIp; - } - - public void setWanIp(String wanIp) { - this.wanIp = wanIp; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java b/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java index 3a503a896..67a0e4f7a 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ServiceInfo.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.conf; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; @@ -9,17 +10,14 @@ import org.springframework.stereotype.Component; @Component public class ServiceInfo implements ApplicationListener { + @Getter private static int serverPort; - public static int getServerPort() { - return serverPort; - } - @Override public void onApplicationEvent(WebServerInitializedEvent event) { // 项目启动获取启动的端口号 ServiceInfo.serverPort = event.getWebServer().getPort(); - log.info("项目启动获取启动的端口号: " + ServiceInfo.serverPort); + log.info("项目启动获取启动的端口号: {}", ServiceInfo.serverPort); } public void setServerPort(int serverPort) { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java index db5b6b4fc..245aba36f 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java @@ -98,4 +98,12 @@ public class SpringDocConfig { .packagesToScan("com.genersoft.iot.vmp.user") .build(); } + + @Bean + public GroupedOpenApi publicApi7() { + return GroupedOpenApi.builder() + .group("6. 部标设备") + .packagesToScan("com.genersoft.iot.vmp.jt1078.controller") + .build(); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java index 26130a02b..26302e782 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java @@ -204,6 +204,9 @@ public class UserSetting { */ private boolean sipCacheServerConnections = true; - + /** + * 禁用date头,变相禁用了校时 + */ + private boolean disableDateHeader = false; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FileCallback.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FileCallback.java new file mode 100644 index 000000000..aa564fcd7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FileCallback.java @@ -0,0 +1,8 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import java.io.OutputStream; + +public interface FileCallback { + + OutputStream run(String path); +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpAuthority.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpAuthority.java new file mode 100644 index 000000000..0ccb14452 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpAuthority.java @@ -0,0 +1,17 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import org.apache.ftpserver.ftplet.Authority; +import org.apache.ftpserver.ftplet.AuthorizationRequest; + +public class FtpAuthority implements Authority { + + @Override + public boolean canAuthorize(AuthorizationRequest authorizationRequest) { + return true; + } + + @Override + public AuthorizationRequest authorize(AuthorizationRequest authorizationRequest) { + return authorizationRequest; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemFactory.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemFactory.java new file mode 100644 index 000000000..5d771e9e0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemFactory.java @@ -0,0 +1,33 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import org.apache.ftpserver.ftplet.FileSystemFactory; +import org.apache.ftpserver.ftplet.FileSystemView; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class FtpFileSystemFactory implements FileSystemFactory { + + private final Map outputStreamMap = new ConcurrentHashMap<>(); + + @Override + public FileSystemView createFileSystemView(User user) throws FtpException { + return new FtpFileSystemView(user, path -> { + return outputStreamMap.get(path); + }); + } + + public void addOutputStream(String filePath, OutputStream outputStream) { + outputStreamMap.put(filePath, outputStream); + } + + public void removeOutputStream(String filePath) { + outputStreamMap.remove(filePath); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemView.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemView.java new file mode 100644 index 000000000..e7e8d3d57 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpFileSystemView.java @@ -0,0 +1,63 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import org.apache.ftpserver.ftplet.FileSystemView; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.FtpFile; +import org.apache.ftpserver.ftplet.User; + +import java.io.OutputStream; + +public class FtpFileSystemView implements FileSystemView { + + private User user; + + private FileCallback fileCallback; + + public FtpFileSystemView(User user, FileCallback fileCallback) { + this.user = user; + this.fileCallback = fileCallback; + } + + public static String HOME_PATH = "root"; + + public FtpFile workDir = VirtualFtpFile.getDir(HOME_PATH); + + @Override + public FtpFile getHomeDirectory() throws FtpException { + return VirtualFtpFile.getDir(HOME_PATH); + } + + @Override + public FtpFile getWorkingDirectory() throws FtpException { + return workDir; + } + + @Override + public boolean changeWorkingDirectory(String dir) throws FtpException { + workDir = VirtualFtpFile.getDir(dir); + return true; + } + + @Override + public FtpFile getFile(String file) throws FtpException { + VirtualFtpFile ftpFile = VirtualFtpFile.getFile(file); + if (fileCallback != null) { + OutputStream outputStream = fileCallback.run(workDir.getName()); + if (outputStream != null) { + ftpFile.setOutputStream(outputStream); + } + } + return ftpFile; + } + + @Override + public boolean isRandomAccessible() throws FtpException { + return true; + } + + @Override + public void dispose() { + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpServerConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpServerConfig.java new file mode 100644 index 000000000..d04016305 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpServerConfig.java @@ -0,0 +1,69 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import lombok.extern.slf4j.Slf4j; +import org.apache.ftpserver.*; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.listener.Listener; +import org.apache.ftpserver.listener.ListenerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@ConditionalOnProperty(value = "ftp.enable", havingValue = "true") +@Slf4j +public class FtpServerConfig { + + @Autowired + private UserManager userManager; + + @Autowired + private FtpFileSystemFactory fileSystemFactory; + + @Autowired + private Ftplet ftplet; + + @Autowired + private FtpSetting ftpSetting; + + /** + * ftp server init + */ + @Bean + public FtpServer ftpServer() { + FtpServerFactory serverFactory = new FtpServerFactory(); + ListenerFactory listenerFactory = new ListenerFactory(); + // 1、设置服务端口 + listenerFactory.setPort(ftpSetting.getPort()); + // 2、设置被动模式数据上传的接口范围,云服务器需要开放对应区间的端口给客户端 + DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory(); + dataConnectionConfFactory.setPassivePorts(ftpSetting.getPassivePorts()); + listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration()); + // 4、替换默认的监听器 + Listener listener = listenerFactory.createListener(); + serverFactory.addListener("default", listener); + // 5、配置自定义用户事件 + Map ftpLets = new HashMap<>(); + ftpLets.put("ftpService", ftplet); + serverFactory.setFtplets(ftpLets); + // 6、读取用户的配置信息 + // 6.2、设置用信息 + serverFactory.setUserManager(userManager); + serverFactory.setFileSystem(fileSystemFactory); + // 7、实例化FTP Server + FtpServer server = serverFactory.createServer(); + try { + server.start(); + if (!server.isStopped()) { + log.info("[FTP服务] 已启动, 端口: {}", ftpSetting.getPort()); + } + } catch (FtpException e) { + log.info("[FTP服务] 启动失败 ", e); + } + return server; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpSetting.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpSetting.java new file mode 100644 index 000000000..91acc8bb0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/FtpSetting.java @@ -0,0 +1,21 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 配置文件 user-settings 映射的配置信息 + */ +@Component +@ConfigurationProperties(prefix = "ftp", ignoreInvalidFields = true) +@Order(0) +@Data +public class FtpSetting { + + private Boolean enable = Boolean.FALSE; + + private int port = 21; + private String passivePorts = "10000-10500"; +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/Ftplet.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/Ftplet.java new file mode 100644 index 000000000..b413a427d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/Ftplet.java @@ -0,0 +1,59 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent; +import org.apache.ftpserver.ftplet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import java.io.IOException; + + +@Component +public class Ftplet extends DefaultFtplet { + + private final Logger logger = LoggerFactory.getLogger(Ftplet.class); + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @Override + public FtpletResult onUploadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { + FtpFile file = session.getFileSystemView().getFile(request.getArgument()); + if (file == null) { + return super.onUploadEnd(session, request); + } + sendEvent(file.getAbsolutePath()); + return super.onUploadUniqueEnd(session, request); + } + + @Override + public FtpletResult onAppendEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { + FtpFile file = session.getFileSystemView().getFile(request.getArgument()); + if (file == null) { + return super.onUploadEnd(session, request); + } + sendEvent(file.getAbsolutePath()); + return super.onUploadUniqueEnd(session, request); + } + + @Override + public FtpletResult onUploadUniqueEnd(FtpSession session, FtpRequest request) throws FtpException, IOException { + FtpFile file = session.getFileSystemView().getFile(request.getArgument()); + if (file == null) { + return super.onUploadEnd(session, request); + } + sendEvent(file.getAbsolutePath()); + return super.onUploadUniqueEnd(session, request); + } + + private void sendEvent(String filePath){ + FtpUploadEvent event = new FtpUploadEvent(this); + logger.info("[文件已上传]: {}", filePath); + event.setFileName(filePath); + applicationEventPublisher.publishEvent(event); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/UserManager.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/UserManager.java new file mode 100644 index 000000000..da5c5ed69 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/UserManager.java @@ -0,0 +1,86 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.ftpserver.ftplet.*; +import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@Component +public class UserManager implements org.apache.ftpserver.ftplet.UserManager { + + private static final String PREFIX = "VMP_FTP_USER_"; + + @Autowired + private RedisTemplate redisTemplate; + + + @Override + public User getUserByName(String username) throws FtpException { + return (BaseUser)redisTemplate.opsForValue().get(PREFIX + username); + } + + @Override + public String[] getAllUserNames() throws FtpException { + return new String[0]; + } + + @Override + public void delete(String username) throws FtpException { + + } + + @Override + public void save(User user) throws FtpException {} + + @Override + public boolean doesExist(String username) throws FtpException { + return redisTemplate.opsForValue().get(PREFIX + username) != null; + } + + @Override + public User authenticate(Authentication authentication) throws AuthenticationFailedException { + UsernamePasswordAuthentication usernamePasswordAuthentication = (UsernamePasswordAuthentication) authentication; + BaseUser user = (BaseUser)redisTemplate.opsForValue().get(PREFIX + usernamePasswordAuthentication.getUsername()); + if (user != null && usernamePasswordAuthentication.getPassword().equals(user.getPassword())) { + return user; + } + return null; + } + + @Override + public String getAdminName() throws FtpException { + return null; + } + + @Override + public boolean isAdmin(String username) throws FtpException { + return false; + } + + public BaseUser getRandomUser(){ + BaseUser use = new BaseUser(); + use.setName(RandomStringUtils.randomAlphabetic(6).toLowerCase()); + use.setPassword(RandomStringUtils.randomAlphabetic(6).toLowerCase()); + use.setEnabled(true); + use.setHomeDirectory("/"); + List authorities = new ArrayList<>(); + authorities.add(new FtpAuthority()); + use.setAuthorities(authorities); + String key = PREFIX + use.getName(); + + // 随机用户信息十分钟自动失效 + Duration duration = Duration.ofMinutes(10); + redisTemplate.opsForValue().set(key, use, duration); + return use; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/VirtualFtpFile.java b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/VirtualFtpFile.java new file mode 100644 index 000000000..5ee48ab0b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ftpServer/VirtualFtpFile.java @@ -0,0 +1,166 @@ +package com.genersoft.iot.vmp.conf.ftpServer; + +import lombok.Setter; +import org.apache.ftpserver.ftplet.FtpFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; + +public class VirtualFtpFile implements FtpFile { + + @Setter + private String name; + + @Setter + private boolean hidden = false; + + @Setter + private boolean directory = false; + + @Setter + private String ownerName; + + private Long lastModified = null; + + @Setter + private long size = 0; + + @Setter + private OutputStream outputStream; + + public static VirtualFtpFile getFile(String name) { + VirtualFtpFile virtualFtpFile = new VirtualFtpFile(); + virtualFtpFile.setName(name); + return virtualFtpFile; + } + + public static VirtualFtpFile getDir(String name) { + if (name.endsWith("/")) { + name = name.replaceAll("/", ""); + } + VirtualFtpFile virtualFtpFile = new VirtualFtpFile(); + virtualFtpFile.setName(name); + virtualFtpFile.setDirectory(true); + return virtualFtpFile; + } + + @Override + public String getAbsolutePath() { + return FtpFileSystemView.HOME_PATH + "/" + name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isHidden() { + return hidden; + } + + @Override + public boolean isDirectory() { + return directory; + } + + @Override + public boolean isFile() { + return !directory; + } + + @Override + public boolean doesExist() { + return false; + } + + @Override + public boolean isReadable() { + return true; + } + + @Override + public boolean isWritable() { + return true; + } + + @Override + public boolean isRemovable() { + return true; + } + + @Override + public String getOwnerName() { + return ownerName; + } + + @Override + public String getGroupName() { + return "root"; + } + + @Override + public int getLinkCount() { + return 0; + } + + @Override + public long getLastModified() { + if (lastModified == null) { + lastModified = System.currentTimeMillis(); + } + return lastModified; + } + + @Override + public boolean setLastModified(long time) { + lastModified = time; + return true; + } + + @Override + public long getSize() { + return size; + } + + @Override + public Object getPhysicalFile() { + System.err.println("getPhysicalFile"); + return null; + } + + @Override + public boolean mkdir() { + return true; + } + + @Override + public boolean delete() { + return true; + } + + @Override + public boolean move(FtpFile destination) { + this.name = destination.getName(); + return true; + } + + @Override + public List listFiles() { + return Collections.emptyList(); + } + + @Override + public OutputStream createOutputStream(long offset) throws IOException { + return outputStream; + } + + @Override + public InputStream createInputStream(long offset) throws IOException { + System.out.println("createInputStream----"); + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java index ead227f3d..f6528a0e7 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java @@ -58,7 +58,7 @@ public class JwtUtils implements InitializingBean { private static IUserService userService; private static IUserApiKeyService userApiKeyService; - + private static UserSetting userSetting; public static String getApiKeyHeader() { @@ -236,7 +236,7 @@ public class JwtUtils implements InitializingBean { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON); } else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); - } + } } else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index e94edc6a6..709933719 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -99,6 +99,8 @@ public class WebSecurityConfig { defaultExcludes.add("/index/hook/**"); defaultExcludes.add("/api/device/query/snap/**"); defaultExcludes.add("/index/hook/abl/**"); + defaultExcludes.add("/api/jt1078/playback/download"); + defaultExcludes.add("/api/jt1078/snap"); if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) { defaultExcludes.addAll(userSetting.getInterfaceAuthenticationExcludes()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java index 39e121fdf..4d302fec4 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java @@ -150,6 +150,9 @@ public class CommonGBChannel { @Schema(description = "更新时间") private String updateTime; + @Schema(description = "流唯一编号,存在表示正在直播") + private String streamId; + public String encode(String serverDeviceId) { return encode(null, serverDeviceId); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonRecordInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonRecordInfo.java new file mode 100644 index 000000000..90d8306aa --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonRecordInfo.java @@ -0,0 +1,17 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import lombok.Data; + +@Data +public class CommonRecordInfo { + + // 开始时间 + private String startTime; + + // 结束时间 + private String endTime; + + // 文件大小 单位byte + private String fileSize; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java index ab35ac51a..94010da63 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java @@ -21,6 +21,12 @@ public class DeviceChannel extends CommonGBChannel { @Schema(description = "数据库自增ID") private int id; + @Schema(description = "父设备编码") + private String parentDeviceId; + + @Schema(description = "父设备名称") + private String parentName; + @MessageElementForCatalog("DeviceID") @Schema(description = "编码") private String deviceId; @@ -173,9 +179,6 @@ public class DeviceChannel extends CommonGBChannel { @Schema(description = "子设备数") private int subCount; - @Schema(description = "流唯一编号,存在表示正在直播") - private String streamId; - @Schema(description = "是否含有音频") private boolean hasAudio; @@ -189,7 +192,7 @@ public class DeviceChannel extends CommonGBChannel { @Schema(description = "通道类型, 默认0, 0: 普通通道,1 行政区划 2 业务分组/虚拟组织") private int channelType; - private Integer dataType = ChannelDataType.GB28181.value; + private Integer dataType = ChannelDataType.GB28181; public void setPtzType(int ptzType) { this.ptzType = ptzType; @@ -249,7 +252,7 @@ public class DeviceChannel extends CommonGBChannel { commonGBChannel.setGbId(id); commonGBChannel.setGbDeviceId(deviceId); commonGBChannel.setGbName(name); - commonGBChannel.setDataType(ChannelDataType.GB28181.value); + commonGBChannel.setDataType(ChannelDataType.GB28181); commonGBChannel.setDataDeviceId(getDataDeviceId()); return commonGBChannel; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java index df81d3395..4dc994a40 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java @@ -14,7 +14,7 @@ public class FrontEndControlCodeForAuxiliary implements IFrontEndControlCode { } /** - * 辅助开关控制指令: 1为开, 2为关, 3为设置自动扫描右边界, 4为设置自动扫描速度 + * 辅助开关控制指令: 1为开, 2为关 */ @Getter @Setter diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java index f959ea552..8212d6aea 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java @@ -27,6 +27,13 @@ public class FrontEndControlCodeForPreset implements IFrontEndControlCode { @Setter private Integer presetId; + /** + * 预置位名称 + */ + @Getter + @Setter + private String presetName; + @Override public String encode() { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java index 3bb724437..ce16537ce 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java @@ -14,7 +14,7 @@ public class FrontEndControlCodeForScan implements IFrontEndControlCode { } /** - * 预置位指令: 1为开始自动扫描, 2为设置自动扫描左边界, 3为设置自动扫描右边界, 4为设置自动扫描速度 + * 预置位指令: 1为开始自动扫描, 2为设置自动扫描左边界, 3为设置自动扫描右边界, 4为设置自动扫描速度, 5为停止自动扫描 */ @Getter @Setter diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java index 2e27dc318..91ecb2543 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java @@ -14,7 +14,7 @@ public class FrontEndControlCodeForTour implements IFrontEndControlCode { } /** - * 巡航指令: 1为加入巡航点, 2为删除一个巡航点, 3为设置巡航速度, 4为设置巡航停留时间, 5为开始巡航 + * 巡航指令: 1为加入巡航点, 2为删除一个巡航点, 3为设置巡航速度, 4为设置巡航停留时间, 5为开始巡航, 6为停止巡航 */ @Getter @Setter diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForWiper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForWiper.java new file mode 100644 index 000000000..9e1af0e01 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForWiper.java @@ -0,0 +1,27 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForWiper implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.AUXILIARY; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 辅助开关控制指令: 1为开, 2为关 + */ + @Getter + @Setter + private Integer code; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/MessageResponseTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/MessageResponseTask.java new file mode 100644 index 000000000..cb3dfb0d4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/MessageResponseTask.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import lombok.Getter; +import lombok.Setter; +import org.dom4j.Element; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +public class MessageResponseTask implements Delayed { + + @Getter + @Setter + private Element element; + + @Getter + @Setter + private List data; + + @Getter + @Setter + private String key; + + + /** + * 超时时间(单位: 毫秒) + */ + @Getter + @Setter + private long delayTime; + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + @Override + public int compareTo(@NotNull Delayed o) { + return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java index 5b524cf67..130b768c5 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java @@ -2,15 +2,19 @@ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; import java.time.Instant; import java.util.List; -/** - * @description:设备录像信息bean +/** + * @description:设备录像信息bean * @author: swwheihei - * @date: 2020年5月8日 下午2:05:56 + * @date: 2020年5月8日 下午2:05:56 */ +@Setter +@Getter @Schema(description = "设备录像查询结果信息") public class RecordInfo { @@ -36,67 +40,4 @@ public class RecordInfo { @Schema(description = "") private List recordList; - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getSumNum() { - return sumNum; - } - - public void setSumNum(int sumNum) { - this.sumNum = sumNum; - } - - public List getRecordList() { - return recordList; - } - - public void setRecordList(List recordList) { - this.recordList = recordList; - } - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } - - public String getSn() { - return sn; - } - - public void setSn(String sn) { - this.sn = sn; - } - - public Instant getLastTime() { - return lastTime; - } - - public void setLastTime(Instant lastTime) { - this.lastTime = lastTime; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java index 452e13886..bc7630b34 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java @@ -3,16 +3,20 @@ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; import org.jetbrains.annotations.NotNull; import java.time.Instant; import java.time.temporal.TemporalAccessor; /** - * @description:设备录像bean + * @description:设备录像bean * @author: swwheihei - * @date: 2020年5月8日 下午2:06:54 + * @date: 2020年5月8日 下午2:06:54 */ +@Setter +@Getter @Schema(description = "设备录像详情") public class RecordItem implements Comparable{ @@ -40,93 +44,13 @@ public class RecordItem implements Comparable{ @Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密") private int secrecy; - @Schema(description = "录像产生类型(可选)time或alarm 或 manua") + @Schema(description = "录像产生类型(可选)time或alarm 或 manual") private String type; @Schema(description = "录像触发者ID(可选)") private String recorderId; - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - - public int getSecrecy() { - return secrecy; - } - - public void setSecrecy(int secrecy) { - this.secrecy = secrecy; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getRecorderId() { - return recorderId; - } - - public void setRecorderId(String recorderId) { - this.recorderId = recorderId; - } - - public String getFileSize() { - return fileSize; - } - - public void setFileSize(String fileSize) { - this.fileSize = fileSize; - } - - @Override + @Override public int compareTo(@NotNull RecordItem recordItem) { TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime); TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipSendFailEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipSendFailEvent.java new file mode 100644 index 000000000..2e2c54e5c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipSendFailEvent.java @@ -0,0 +1,19 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import lombok.Data; + +@Data +public class SipSendFailEvent extends SipSubscribe.EventResult { + + private String callId; + + private String msg; + + public static SipSendFailEvent getInstance(String callId, String msg){ + SipSendFailEvent sipSendFailEvent = new SipSendFailEvent(); + sipSendFailEvent.setMsg(msg); + sipSendFailEvent.setCallId(callId); + return sipSendFailEvent; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java similarity index 67% rename from src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java rename to src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java index c13637a88..30ca522f0 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java @@ -2,11 +2,9 @@ package com.genersoft.iot.vmp.gb28181.controller; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; -import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; -import com.genersoft.iot.vmp.gb28181.bean.DeviceType; -import com.genersoft.iot.vmp.gb28181.bean.IndustryCodeType; -import com.genersoft.iot.vmp.gb28181.bean.NetworkIdentificationType; +import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToGroupByGbDeviceParam; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToGroupParam; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToRegionByGbDeviceParam; @@ -17,6 +15,8 @@ import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; @@ -35,13 +35,14 @@ import jakarta.servlet.http.HttpServletRequest; import java.net.MalformedURLException; import java.net.URL; import java.util.List; +import java.util.concurrent.TimeUnit; @Tag(name = "全局通道管理") @RestController @Slf4j @RequestMapping(value = "/api/common/channel") -public class CommonChannelController { +public class ChannelController { @Autowired private IRedisCatchStorage redisCatchStorage; @@ -276,7 +277,94 @@ public class CommonChannelController { @Operation(summary = "播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping("/play") - public DeferredResult> deleteChannelToGroupByGbDevice(HttpServletRequest request, Integer channelId){ + public DeferredResult> play(HttpServletRequest request, Integer channelId){ + Assert.notNull(channelId,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); + + ErrorCallback callback = (code, msg, streamInfo) -> { + if (code == InviteErrorCode.SUCCESS.getCode()) { + WVPResult wvpResult = WVPResult.success(); + if (streamInfo != null) { + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo=streamInfo.clone();//深拷贝 + String host; + try { + URL url=new URL(request.getRequestURL().toString()); + host=url.getHost(); + } catch (MalformedURLException e) { + host=request.getLocalAddr(); + } + streamInfo.changeStreamIp(host); + } + if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) + && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { + streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); + } + wvpResult.setData(new StreamContent(streamInfo)); + }else { + wvpResult.setCode(code); + wvpResult.setMsg(msg); + } + result.setResult(wvpResult); + }else { + result.setResult(WVPResult.fail(code, msg)); + } + }; + channelPlayService.play(channel, null, userSetting.getRecordSip(), callback); + return result; + } + + @Operation(summary = "停止播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @GetMapping("/play/stop") + public void stopPlay(Integer channelId){ + Assert.notNull(channelId,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.stopPlay(channel, channel.getStreamId()); + } + + @Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "startTime", description = "开始时间", required = true) + @Parameter(name = "endTime", description = "结束时间", required = true) + @GetMapping("/playback/query") + public DeferredResult>> queryRecord(Integer channelId, String startTime, String endTime){ + + DeferredResult>> result = new DeferredResult<>(Long.valueOf(userSetting.getRecordInfoTimeout()), TimeUnit.MILLISECONDS); + if (!DateUtil.verification(startTime, DateUtil.formatter)){ + throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime格式为" + DateUtil.PATTERN); + } + if (!DateUtil.verification(endTime, DateUtil.formatter)){ + throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime格式为" + DateUtil.PATTERN); + } + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) -> { + WVPResult> wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }); + result.onTimeout(()->{ + WVPResult> wvpResult = new WVPResult<>(); + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg("timeout"); + result.setResult(wvpResult); + }); + return result; + } + + @Operation(summary = "录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "startTime", description = "开始时间", required = true) + @Parameter(name = "endTime", description = "结束时间", required = true) + @GetMapping("/playback") + public DeferredResult> playback(HttpServletRequest request, Integer channelId, String startTime, String endTime){ Assert.notNull(channelId,"参数异常"); CommonGBChannel channel = channelService.getOne(channelId); Assert.notNull(channel, "通道不存在"); @@ -313,7 +401,67 @@ public class CommonChannelController { result.setResult(WVPResult.fail(code, msg)); } }; - channelPlayService.play(channel, null, userSetting.getRecordSip(), callback); + channelPlayService.playback(channel, DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime), + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime), callback); return result; } + + @Operation(summary = "停止录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @GetMapping("/playback/stop") + public void stopPlayback(Integer channelId, String stream){ + Assert.notNull(channelId,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.stopPlayback(channel, stream); + } + + @Operation(summary = "暂停录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @GetMapping("/playback/pause") + public void pausePlayback(Integer channelId, String stream){ + Assert.notNull(channelId,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.playbackPause(channel, stream); + } + + @Operation(summary = "恢复录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @GetMapping("/playback/resume") + public void resumePlayback(Integer channelId, String stream){ + Assert.notNull(channelId,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.playbackResume(channel, stream); + } + + @Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @Parameter(name = "seekTime", description = "将要播放的时间", required = true) + @GetMapping("/playback/seek") + public void seekPlayback(Integer channelId, String stream, Long seekTime){ + Assert.notNull(channelId,"参数异常"); + Assert.notNull(seekTime,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.playbackSeek(channel, stream, seekTime); + } + + @Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @Parameter(name = "speed", description = "倍速", required = true) + @GetMapping("/playback/speed") + public void seekPlayback(Integer channelId, String stream, Double speed){ + Assert.notNull(channelId,"参数异常"); + Assert.notNull(speed,"参数异常"); + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + channelPlayService.playbackSpeed(channel, stream, speed); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelFrontEndController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelFrontEndController.java new file mode 100755 index 000000000..8659cf245 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelFrontEndController.java @@ -0,0 +1,601 @@ +package com.genersoft.iot.vmp.gb28181.controller; + +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.conf.security.JwtUtils; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; + +import java.util.List; + + +@Tag(name = "全局通道前端控制") +@RestController +@Slf4j +@RequestMapping(value = "/api/common/channel/front-end") +public class ChannelFrontEndController { + + @Autowired + private IGbChannelService channelService; + + @Autowired + private IGbChannelControlService channelControlService; + + + @Operation(summary = "云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道ID", required = true) + @Parameter(name = "command", description = "控制指令,允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop", required = true) + @Parameter(name = "panSpeed", description = "水平速度(0-100)", required = true) + @Parameter(name = "tiltSpeed", description = "垂直速度(0-100)", required = true) + @Parameter(name = "zoomSpeed", description = "缩放速度(0-100)", required = true) + @GetMapping("/ptz") + public DeferredResult> ptz(Integer channelId, String command, Integer panSpeed, Integer tiltSpeed, Integer zoomSpeed){ + + if (log.isDebugEnabled()) { + log.debug("[通用通道]云台控制 API调用,channelId:{} ,command:{} ,panSpeed:{} ,tiltSpeed:{} ,zoomSpeed:{}",channelId, command, panSpeed, tiltSpeed, zoomSpeed); + } + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + if (panSpeed == null) { + panSpeed = 50; + }else if (panSpeed < 0 || panSpeed > 100) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "panSpeed 为 0-100的数字"); + } + if (tiltSpeed == null) { + tiltSpeed = 50; + }else if (tiltSpeed < 0 || tiltSpeed > 100) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "tiltSpeed 为 0-100的数字"); + } + if (zoomSpeed == null) { + zoomSpeed = 50; + }else if (zoomSpeed < 0 || zoomSpeed > 100) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "zoomSpeed 为 0-100的数字"); + } + + FrontEndControlCodeForPTZ controlCode = new FrontEndControlCodeForPTZ(); + controlCode.setPanSpeed(panSpeed); + controlCode.setTiltSpeed(tiltSpeed); + controlCode.setZoomSpeed(zoomSpeed); + switch (command){ + case "left": + controlCode.setPan(0); + break; + case "right": + controlCode.setPan(1); + break; + case "up": + controlCode.setTilt(0); + break; + case "down": + controlCode.setTilt(1); + break; + case "upleft": + controlCode.setPan(0); + controlCode.setTilt(0); + break; + case "upright": + controlCode.setTilt(0); + controlCode.setPan(1); + break; + case "downleft": + controlCode.setPan(0); + controlCode.setTilt(1); + break; + case "downright": + controlCode.setTilt(1); + controlCode.setPan(1); + break; + case "zoomin": + controlCode.setZoom(1); + break; + case "zoomout": + controlCode.setZoom(0); + break; + default: + break; + } + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + channelControlService.ptz(channel, controlCode, (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }); + return result; + } + + + @Operation(summary = "光圈控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "command", description = "控制指令,允许值: in, out, stop", required = true) + @Parameter(name = "speed", description = "光圈速度(0-100)", required = true) + @GetMapping("/fi/iris") + public DeferredResult> iris(Integer channelId, String command, Integer speed){ + + if (log.isDebugEnabled()) { + log.debug("[通用通道]光圈控制 API调用,channelId:{} ,command:{} ,speed:{} ",channelId, command, speed); + } + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + if (speed == null) { + speed = 50; + }else if (speed < 0 || speed > 100) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-100的数字"); + } + + FrontEndControlCodeForFI controlCode = new FrontEndControlCodeForFI(); + controlCode.setIrisSpeed(speed); + + switch (command){ + case "in": + controlCode.setIris(1); + break; + case "out": + controlCode.setIris(0); + break; + default: + break; + } + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.fi(channel, controlCode, callback); + + return result; + } + + @Operation(summary = "聚焦控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "command", description = "控制指令,允许值: near, far, stop", required = true) + @Parameter(name = "speed", description = "聚焦速度(0-100)", required = true) + @GetMapping("/fi/focus") + public DeferredResult> focus(Integer channelId, String command, Integer speed){ + + if (log.isDebugEnabled()) { + log.debug("[通用通道]聚焦控制 API调用,channelId:{} ,command:{} ,speed:{} ", channelId, command, speed); + } + + if (speed == null) { + speed = 50; + }else if (speed < 0 || speed > 100) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "speed 为 0-100的数字"); + } + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + FrontEndControlCodeForFI controlCode = new FrontEndControlCodeForFI(); + controlCode.setFocusSpeed(speed); + switch (command){ + case "near": + controlCode.setFocus(0); + break; + case "far": + controlCode.setFocus(1); + break; + default: + break; + } + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.fi(channel, controlCode, callback); + return result; + } + + @Operation(summary = "查询预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @GetMapping("/preset/query") + public DeferredResult>> queryPreset(Integer channelId) { + if (log.isDebugEnabled()) { + log.debug("[通用通道] 预置位查询API调用, {}", channelId); + } + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + DeferredResult>> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult> wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback> callback = (code, msg, data) -> { + WVPResult> wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.queryPreset(channel, callback); + + return result; + } + + private DeferredResult> controlPreset(Integer channelId, FrontEndControlCodeForPreset controlCode) { + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.preset(channel, controlCode, callback); + return result; + } + + @Operation(summary = "预置位指令-设置预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "presetId", description = "预置位编号", required = true) + @Parameter(name = "presetName", description = "预置位名称", required = true) + @GetMapping("/preset/add") + public DeferredResult> addPreset(Integer channelId, Integer presetId, String presetName) { + FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); + controlCode.setCode(1); + controlCode.setPresetId(presetId); + controlCode.setPresetName(presetName); + + return controlPreset(channelId, controlCode); + } + + @Operation(summary = "预置位指令-调用预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "presetId", description = "预置位编号(1-100)", required = true) + @GetMapping("/preset/call") + public DeferredResult> callPreset(Integer channelId, Integer presetId) { + FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); + controlCode.setCode(2); + controlCode.setPresetId(presetId); + + return controlPreset(channelId, controlCode); + } + + @Operation(summary = "预置位指令-删除预置位", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "presetId", description = "预置位编号(1-100)", required = true) + @GetMapping("/preset/delete") + public DeferredResult> deletePreset(Integer channelId, Integer presetId) { + + FrontEndControlCodeForPreset controlCode = new FrontEndControlCodeForPreset(); + controlCode.setCode(3); + controlCode.setPresetId(presetId); + + return controlPreset(channelId, controlCode); + } + + private DeferredResult> tourControl(Integer channelId, FrontEndControlCodeForTour controlCode) { + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.tour(channel, controlCode, callback); + return result; + } + + @Operation(summary = "巡航指令-加入巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号", required = true) + @Parameter(name = "presetId", description = "预置位编号", required = true) + @GetMapping("/tour/point/add") + public DeferredResult> addTourPoint(Integer channelId, Integer tourId, Integer presetId) { + + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(1); + controlCode.setPresetId(presetId); + controlCode.setTourId(tourId); + + return tourControl(channelId, controlCode); + } + + @Operation(summary = "巡航指令-删除一个巡航点", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号(1-100)", required = true) + @Parameter(name = "presetId", description = "预置位编号(0-100, 为0时删除整个巡航)", required = true) + @GetMapping("/tour/point/delete") + public DeferredResult> deleteCruisePoint(Integer channelId, Integer tourId, Integer presetId) { + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(2); + controlCode.setPresetId(presetId); + controlCode.setTourId(tourId); + + return tourControl(channelId, controlCode); + } + + @Operation(summary = "巡航指令-设置巡航速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号(0-100)", required = true) + @Parameter(name = "speed", description = "巡航速度(1-4095)", required = true) + @Parameter(name = "presetId", description = "预置位编号", required = true) + @GetMapping("/tour/speed") + public DeferredResult> setCruiseSpeed(Integer channelId, Integer tourId, Integer speed, Integer presetId) { + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(3); + controlCode.setTourSpeed(speed); + controlCode.setTourId(tourId); + controlCode.setPresetId(presetId); + return tourControl(channelId, controlCode); + } + + @Operation(summary = "巡航指令-设置巡航停留时间", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号", required = true) + @Parameter(name = "time", description = "巡航停留时间(1-4095)", required = true) + @Parameter(name = "presetId", description = "预置位编号", required = true) + @GetMapping("/tour/time") + public DeferredResult> setCruiseTime(Integer channelId, Integer tourId, Integer time, Integer presetId) { + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(4); + controlCode.setTourTime(time); + controlCode.setTourId(tourId); + controlCode.setPresetId(presetId); + return tourControl(channelId, controlCode); + } + + @Operation(summary = "巡航指令-开始巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号)", required = true) + @GetMapping("/tour/start") + public DeferredResult> startCruise(Integer channelId, Integer tourId) { + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(5); + controlCode.setTourId(tourId); + return tourControl(channelId, controlCode); + } + + @Operation(summary = "巡航指令-停止巡航", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "tourId", description = "巡航组号", required = true) + @GetMapping("/tour/stop") + public DeferredResult> stopCruise(Integer channelId, Integer tourId) { + FrontEndControlCodeForTour controlCode = new FrontEndControlCodeForTour(); + controlCode.setCode(6); + controlCode.setTourId(tourId); + return tourControl(channelId, controlCode); + } + + private DeferredResult> scanControl(Integer channelId, FrontEndControlCodeForScan controlCode) { + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + channelControlService.scan(channel, controlCode, callback); + + return result; + + } + + @Operation(summary = "扫描指令-开始自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) + @GetMapping("/scan/start") + public DeferredResult> startScan(Integer channelId, Integer scanId) { + FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); + controlCode.setCode(1); + controlCode.setScanId(scanId); + return scanControl(channelId, controlCode); + + } + + @Operation(summary = "扫描指令-停止自动扫描", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) + @GetMapping("/scan/stop") + public DeferredResult> stopScan(Integer channelId, Integer scanId) { + FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); + controlCode.setCode(5); + controlCode.setScanId(scanId); + return scanControl(channelId, controlCode); + } + + @Operation(summary = "扫描指令-设置自动扫描左边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) + @GetMapping("/scan/set/left") + public DeferredResult> setScanLeft(Integer channelId, Integer scanId) { + FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); + controlCode.setCode(2); + controlCode.setScanId(scanId); + return scanControl(channelId, controlCode); + } + + @Operation(summary = "扫描指令-设置自动扫描右边界", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) + @GetMapping("/scan/set/right") + public DeferredResult> setScanRight(Integer channelId, Integer scanId) { + FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); + controlCode.setCode(3); + controlCode.setScanId(scanId); + return scanControl(channelId, controlCode); + } + + + @Operation(summary = "扫描指令-设置自动扫描速度", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "scanId", description = "扫描组号(0-100)", required = true) + @Parameter(name = "speed", description = "自动扫描速度(1-4095)", required = true) + @GetMapping("/scan/set/speed") + public DeferredResult> setScanSpeed(Integer channelId, Integer scanId, Integer speed) { + FrontEndControlCodeForScan controlCode = new FrontEndControlCodeForScan(); + controlCode.setCode(4); + controlCode.setScanId(scanId); + controlCode.setScanSpeed(speed); + return scanControl(channelId, controlCode); + } + + + @Operation(summary = "辅助开关控制指令-雨刷控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) + @GetMapping("/wiper") + public DeferredResult> wiper(Integer channelId, String command){ + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + FrontEndControlCodeForWiper controlCode = new FrontEndControlCodeForWiper(); + + switch (command){ + case "on": + controlCode.setCode(1); + break; + case "off": + controlCode.setCode(2); + break; + default: + break; + } + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + + channelControlService.wiper(channel, controlCode, callback); + + return result; + } + + @Operation(summary = "辅助开关控制指令", security = @SecurityRequirement(name = JwtUtils.HEADER)) + + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "command", description = "控制指令,允许值: on, off", required = true) + @Parameter(name = "auxiliaryId", description = "开关编号", required = true) + @GetMapping("/auxiliary") + public DeferredResult> auxiliarySwitch(Integer channelId, String command, Integer auxiliaryId){ + + CommonGBChannel channel = channelService.getOne(channelId); + Assert.notNull(channel, "通道不存在"); + + FrontEndControlCodeForAuxiliary controlCode = new FrontEndControlCodeForAuxiliary(); + controlCode.setAuxiliaryId(auxiliaryId); + switch (command){ + case "on": + controlCode.setCode(1); + break; + case "off": + controlCode.setCode(2); + break; + default: + break; + } + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时"); + result.setResult(wvpResult); + }); + + ErrorCallback callback = (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }; + channelControlService.auxiliary(channel, controlCode, callback); + return result; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceControl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceControl.java index 43a10e1e2..6170cb1b9 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceControl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceControl.java @@ -1,6 +1,6 @@ /** * 设备控制命令API接口 - * + * * @author lawrencehj * @date 2021年2月1日 */ @@ -70,16 +70,16 @@ public class DeviceControl { @Operation(summary = "布防/撤防", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) - @Parameter(name = "guardCmdStr", description = "命令, 可选值:SetGuard(布防),ResetGuard(撤防)", required = true) + @Parameter(name = "guardCmd", description = "命令, 可选值:SetGuard(布防),ResetGuard(撤防)", required = true) @GetMapping("/guard") - public DeferredResult> guardApi(String deviceId, String guardCmdStr) { + public DeferredResult> guardApi(String deviceId, String guardCmd) { if (log.isDebugEnabled()) { log.debug("布防/撤防API调用"); } Device device = deviceService.getDeviceByDeviceId(deviceId); Assert.notNull(device, "设备不存在"); DeferredResult> result = new DeferredResult<>(); - deviceService.guard(device, guardCmdStr, (code, msg, data) -> { + deviceService.guard(device, guardCmd, (code, msg, data) -> { result.setResult(new WVPResult<>(code, msg, data)); }); result.onTimeout(() -> { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java index a0edfc2df..92e171f35 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java @@ -113,6 +113,19 @@ public class DeviceQuery { return deviceChannelService.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count); } + @GetMapping("/streams") + @Operation(summary = "分页查询存在流的通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "page", description = "当前页", required = true) + @Parameter(name = "count", description = "每页查询数量", required = true) + @Parameter(name = "query", description = "查询内容") + public PageInfo streamChannels(int page, int count, + @RequestParam(required = false) String query) { + if (ObjectUtils.isEmpty(query)) { + query = null; + } + + return deviceChannelService.queryChannels(query, true, null, null, true, page, count); + } @Operation(summary = "同步设备通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/GroupController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/GroupController.java index 1284477af..4f9c3bc6a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/GroupController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/GroupController.java @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.GroupTree; import com.genersoft.iot.vmp.gb28181.service.IGroupService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -33,7 +34,7 @@ public class GroupController { groupService.add(group); } - @Operation(summary = "查询分组") + @Operation(summary = "查询分组节点") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "parent", description = "所属分组编号", required = true) @ResponseBody @@ -49,6 +50,17 @@ public class GroupController { return groupService.queryForTree(query, parent, hasChannel); } + @Operation(summary = "查询分组") + @Parameter(name = "query", description = "要搜索的内容", required = true) + @Parameter(name = "channel", description = "true为查询通道,false为查询节点", required = true) + @ResponseBody + @GetMapping("/tree/query") + public PageInfo queryTree(Integer page, Integer count, + @RequestParam(required = true) String query + ){ + return groupService.queryList(page, count, query); + } + @Operation(summary = "更新分组") @Parameter(name = "group", description = "Group", required = true) @ResponseBody diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java index 861594162..a292f58ff 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java @@ -7,19 +7,26 @@ import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.async.DeferredResult; -import jakarta.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequest; +import java.net.MalformedURLException; +import java.net.URL; @Tag(name = "媒体流相关") @@ -52,11 +59,12 @@ public class MediaController { @Parameter(name = "useSourceIpAsStreamIp", description = "是否使用请求IP作为返回的地址IP") @GetMapping(value = "/stream_info_by_app_and_stream") @ResponseBody - public StreamContent getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app, - @RequestParam String stream, - @RequestParam(required = false) String mediaServerId, - @RequestParam(required = false) String callId, - @RequestParam(required = false) Boolean useSourceIpAsStreamIp){ + public DeferredResult> getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app, + @RequestParam String stream, + @RequestParam(required = false) String mediaServerId, + @RequestParam(required = false) String callId, + @RequestParam(required = false) Boolean useSourceIpAsStreamIp){ + DeferredResult> result = new DeferredResult<>(); boolean authority = false; if (callId != null) { // 权限校验 @@ -75,9 +83,7 @@ public class MediaController { authority = true; } } - StreamInfo streamInfo; - if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) { String host = request.getHeader("Host"); String localAddr = host.split(":")[0]; @@ -88,30 +94,37 @@ public class MediaController { } if (streamInfo != null){ - return new StreamContent(streamInfo); + WVPResult wvpResult = WVPResult.success(); + wvpResult.setData(new StreamContent(streamInfo)); + result.setResult(wvpResult); }else { + ErrorCallback callback = (code, msg, streamInfoStoStart) -> { + if (code == InviteErrorCode.SUCCESS.getCode()) { + WVPResult wvpResult = WVPResult.success(); + if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) { + String host; + try { + URL url=new URL(request.getRequestURL().toString()); + host=url.getHost(); + } catch (MalformedURLException e) { + host=request.getLocalAddr(); + } + streamInfoStoStart.changeStreamIp(host); + } + if (!ObjectUtils.isEmpty(streamInfoStoStart.getMediaServer().getTranscodeSuffix()) + && !"null".equalsIgnoreCase(streamInfoStoStart.getMediaServer().getTranscodeSuffix())) { + streamInfoStoStart.setStream(streamInfoStoStart.getStream() + "_" + streamInfoStoStart.getMediaServer().getTranscodeSuffix()); + } + wvpResult.setData(new StreamContent(streamInfoStoStart)); + result.setResult(wvpResult); + }else { + result.setResult(WVPResult.fail(code, msg)); + } + }; //获取流失败,重启拉流后重试一次 - streamProxyService.stopByAppAndStream(app,stream); - boolean start = streamProxyService.startByAppAndStream(app, stream); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - log.error("[线程休眠失败], {}", e.getMessage()); - } - if (useSourceIpAsStreamIp != null && useSourceIpAsStreamIp) { - String host = request.getHeader("Host"); - String localAddr = host.split(":")[0]; - log.info("使用{}作为返回流的ip", localAddr); - streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, localAddr, authority); - }else { - streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, authority); - } - if (streamInfo != null){ - return new StreamContent(streamInfo); - }else { - throw new ControllerException(ErrorCode.ERROR100); - } + streamProxyService.startByAppAndStream(app, stream, callback); } + return result; } /** * 获取推流播放地址 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlaybackController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlaybackController.java index fa6c86a7b..d5e47b57a 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlaybackController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PlaybackController.java @@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.gb28181.controller; -import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; @@ -26,6 +25,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -165,10 +165,10 @@ public class PlaybackController { @Operation(summary = "回放暂停", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @GetMapping("/pause/{streamId}") - public void playPause(@PathVariable String streamId) { + public void playbackPause(@PathVariable String streamId) { log.info("[回放暂停] streamId: {}", streamId); try { - playService.pauseRtp(streamId); + playService.playbackPause(streamId); } catch (ServiceException e) { throw new ControllerException(ErrorCode.ERROR400.getCode(), e.getMessage()); } catch (InvalidArgumentException | ParseException | SipException e) { @@ -183,7 +183,7 @@ public class PlaybackController { public void playResume(@PathVariable String streamId) { log.info("playResume: "+streamId); try { - playService.resumeRtp(streamId); + playService.playbackResume(streamId); } catch (ServiceException e) { throw new ControllerException(ErrorCode.ERROR400.getCode(), e.getMessage()); } catch (InvalidArgumentException | ParseException | SipException e) { @@ -191,23 +191,14 @@ public class PlaybackController { } } - @Operation(summary = "回放拖动播放", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "streamId", description = "回放流ID", required = true) @Parameter(name = "seekTime", description = "拖动偏移量,单位s", required = true) @GetMapping("/seek/{streamId}/{seekTime}") - public void playSeek(@PathVariable String streamId, @PathVariable long seekTime) { + public void playbackSeek(@PathVariable String streamId, @PathVariable long seekTime) { log.info("playSeek: "+streamId+", "+seekTime); - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); - - if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { - log.warn("streamId不存在!"); - throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); - } - Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); - DeviceChannel channel = channelService.getOneById(inviteInfo.getChannelId()); try { - cmder.playSeekCmd(device, channel, inviteInfo.getStreamInfo(), seekTime); + playService.playbackSeek(streamId, seekTime); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } @@ -218,17 +209,10 @@ public class PlaybackController { @Parameter(name = "speed", description = "倍速0.25 0.5 1、2、4、8", required = true) @GetMapping("/speed/{streamId}/{speed}") public void playSpeed(@PathVariable String streamId, @PathVariable Double speed) { + Assert.notNull(speed, "倍速不存在"); log.info("playSpeed: "+streamId+", "+speed); - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); - - if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { - log.warn("streamId不存在!"); - throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); - } - Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId()); - DeviceChannel channel = channelService.getOneById(inviteInfo.getChannelId()); try { - cmder.playSpeedCmd(device, channel, inviteInfo.getStreamInfo(), speed); + playService.playbackSpeed(streamId, speed); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/RegionController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/RegionController.java index d49c28626..ff415df17 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/RegionController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/RegionController.java @@ -50,20 +50,29 @@ public class RegionController { return regionService.query(query, page, count); } - @Operation(summary = "查询区域") + @Operation(summary = "查询区域节点") @Parameter(name = "query", description = "要搜索的内容", required = true) @Parameter(name = "parent", description = "所属行政区划编号", required = true) + @Parameter(name = "hasChannel", description = "是否查询通道", required = true) @ResponseBody @GetMapping("/tree/list") public List queryForTree( - @RequestParam(required = false) String query, @RequestParam(required = false) Integer parent, @RequestParam(required = false) Boolean hasChannel ){ - if (ObjectUtils.isEmpty(query)) { - query = null; - } - return regionService.queryForTree(query, parent, hasChannel); + return regionService.queryForTree(parent, hasChannel); + } + + + @Operation(summary = "查询区域") + @Parameter(name = "query", description = "要搜索的内容", required = true) + @Parameter(name = "channel", description = "true为查询通道,false为查询节点", required = true) + @ResponseBody + @GetMapping("/tree/query") + public PageInfo queryTree(Integer page, Integer count, + @RequestParam(required = true) String query + ){ + return regionService.queryList(page, count, query); } @Operation(summary = "更新区域") diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java index 33d58ced7..3f21f7e38 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java @@ -275,10 +275,8 @@ public interface CommonGBChannelMapper { " true as is_leaf " + " from wvp_device_channel " + " where coalesce(gb_civil_code, civil_code) = #{parentDeviceId} " + - " AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') " + - " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%')) " + " ") - List queryForRegionTreeByCivilCode(@Param("query") String query, @Param("parentDeviceId") String parentDeviceId); + List queryForRegionTreeByCivilCode(@Param("parentDeviceId") String parentDeviceId); @Update(value = {" ") + void updateGps(List commonGBChannels); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java index 8c02a1084..b696e2347 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java @@ -86,10 +86,11 @@ public interface DeviceChannelMapper { int update(DeviceChannel channel); @SelectProvider(type = DeviceChannelProvider.class, method = "queryChannels") - List queryChannels(@Param("dataDeviceId") int dataDeviceId, @Param("civilCode") String civilCode, + List queryChannels(@Param("dataDeviceId") Integer dataDeviceId, @Param("civilCode") String civilCode, @Param("businessGroupId") String businessGroupId, @Param("parentChannelId") String parentChannelId, - @Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel, - @Param("online") Boolean online, @Param("channelIds") List channelIds); + @Param("query") String query, @Param("queryParent") Boolean queryParent, + @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, + @Param("channelIds") List channelIds, @Param("hasStream") Boolean hasStream); @SelectProvider(type = DeviceChannelProvider.class, method = "queryChannelsByDeviceDbId") List queryChannelsByDeviceDbId(@Param("dataDeviceId") int dataDeviceId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java index f99f50eeb..871289d77 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java @@ -252,6 +252,7 @@ public interface DeviceMapper { "mobile_position_submission_interval,"+ "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ + "media_server_id,"+ "as_message_channel,"+ "broadcast_push_after_ack,"+ "geo_coord_sys,"+ diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/GroupMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/GroupMapper.java index 90455443b..5eaf9002f 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/GroupMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/GroupMapper.java @@ -156,7 +156,8 @@ public interface GroupMapper { " wcg.name as gb_name," + " wcg.business_group as gb_business_group_id," + " 1 as gb_parental," + - " wcg.parent_device_id as gb_parent_id" + + " wcg.parent_device_id as gb_parent_id," + + " wcg.civil_code as gb_civil_code" + " from wvp_common_group wcg" + " left join wvp_platform_group wpg on wpg.group_id = wcg.id" + " where wpg.platform_id = #{platformId} " + diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/RegionMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/RegionMapper.java index 5d840b15a..3d67be0da 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/RegionMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/RegionMapper.java @@ -80,9 +80,8 @@ public interface RegionMapper { " where " + " parent_id = #{parentId} " + " parent_id is null " + - " AND (device_id LIKE concat('%',#{query},'%') escape '/' OR name LIKE concat('%',#{query},'%') escape '/') " + " ") - List queryForTree(@Param("query") String query, @Param("parentId") Integer parentId); + List queryForTree(@Param("parentId") Integer parentId); @Delete(""}) + void update(JTChannel channel); + + @Insert(value = {" "}) + @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") + void add(JTChannel channel); + + @Delete("delete from wvp_jt_channel where id = #{id}") + void delete(@Param("id") int id); + + @SelectProvider(type = JTChannelProvider.class, method = "selectChannelByChannelId") + JTChannel selectChannelByChannelId(@Param("terminalDbId") int terminalDbId, @Param("channelId") Integer channelId); + + @SelectProvider(type = JTChannelProvider.class, method = "selectChannelById") + JTChannel selectChannelById(@Param("id") Integer id); +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/dao/JTTerminalMapper.java b/src/main/java/com/genersoft/iot/vmp/jt1078/dao/JTTerminalMapper.java new file mode 100644 index 000000000..c27a42efb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/dao/JTTerminalMapper.java @@ -0,0 +1,115 @@ +package com.genersoft.iot.vmp.jt1078.dao; + +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import org.apache.ibatis.annotations.*; + +import java.util.List; + +@Mapper +public interface JTTerminalMapper { + + @Select("SELECT * FROM wvp_jt_terminal where phone_number=#{phoneNumber}") + JTDevice getDevice(@Param("phoneNumber") String phoneNumber); + + @Update(value = {" "}) + void updateDevice(JTDevice device); + @Select(value = {" "}) + List getDeviceList(@Param("query") String query, @Param("online") Boolean online); + + @Insert("INSERT INTO wvp_jt_terminal (" + + "terminal_id,"+ + "province_id,"+ + "province_text,"+ + "city_id,"+ + "city_text,"+ + "maker_id,"+ + "phone_number,"+ + "model,"+ + "plate_color,"+ + "plate_no,"+ + "longitude,"+ + "latitude,"+ + "create_time,"+ + "register_time,"+ + "update_time"+ + ") VALUES (" + + "#{terminalId}," + + "#{provinceId}," + + "#{provinceText}," + + "#{cityId}," + + "#{cityText}," + + "#{makerId}," + + "#{phoneNumber}," + + "#{model}," + + "#{plateColor}," + + "#{plateNo}," + + "#{longitude}," + + "#{latitude}," + + "#{createTime}," + + "#{registerTime}," + + "#{updateTime}" + + ")") + void addDevice(JTDevice device); + + @Delete("delete from wvp_jt_terminal where phone_number = #{phoneNumber}") + void deleteDeviceByPhoneNumber(@Param("phoneNumber") String phoneNumber); + + @Update(value = {" "}) + void updateDeviceStatus(@Param("connected") boolean connected, @Param("phoneNumber") String phoneNumber); + + @Select("SELECT * FROM wvp_jt_terminal where id=#{deviceId}") + JTDevice getDeviceById(@Param("deviceId") Integer deviceId); + + @Update({""}) + void batchUpdateDevicePosition(List devices); +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/dao/provider/JTChannelProvider.java b/src/main/java/com/genersoft/iot/vmp/jt1078/dao/provider/JTChannelProvider.java new file mode 100644 index 000000000..c06f0ecbd --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/dao/provider/JTChannelProvider.java @@ -0,0 +1,37 @@ +package com.genersoft.iot.vmp.jt1078.dao.provider; + +import java.util.Map; + +public class JTChannelProvider { + + public final static String BASE_SQL = + "SELECT jc.*, jc.id as data_device_id, wdc.*, wdc.id as gb_id " + + " from wvp_jt_channel jc " + + " LEFT join wvp_device_channel wdc " + + " on jc.id = wdc.data_device_id and wdc.data_type = 200 "; + + public String selectChannelByChannelId(Map params ){ + StringBuilder sqlBuild = new StringBuilder(); + sqlBuild.append(BASE_SQL); + sqlBuild.append(" WHERE jc.terminal_db_id = #{terminalDbId} and jc.channel_id = #{channelId} "); + return sqlBuild.toString(); + } + public String selectChannelById(Map params ){ + StringBuilder sqlBuild = new StringBuilder(); + sqlBuild.append(BASE_SQL); + sqlBuild.append(" WHERE jc.id = #{id}"); + return sqlBuild.toString(); + } + public String selectAll(Map params ){ + StringBuilder sqlBuild = new StringBuilder(); + sqlBuild.append(BASE_SQL); + sqlBuild.append(" WHERE jc.terminal_db_id = #{terminalDbId} "); + if (params.get("query") != null) { + sqlBuild.append(" AND ") + .append(" jc.name LIKE ").append("'%").append(params.get("query")).append("%'") + ; + } + sqlBuild.append(" ORDER BY jc.channel_id "); + return sqlBuild.toString(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/event/ConnectChangeEvent.java b/src/main/java/com/genersoft/iot/vmp/jt1078/event/ConnectChangeEvent.java new file mode 100755 index 000000000..cb7ffaa63 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/event/ConnectChangeEvent.java @@ -0,0 +1,29 @@ +package com.genersoft.iot.vmp.jt1078.event; + +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +import java.time.Clock; + +/** + * 链接断或者连接的事件 + */ + +@Setter +@Getter +public class ConnectChangeEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + public ConnectChangeEvent(Object source) { + super(source); + } + + + private boolean connected; + + private String phoneNumber; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/event/DeviceUpdateEvent.java b/src/main/java/com/genersoft/iot/vmp/jt1078/event/DeviceUpdateEvent.java new file mode 100755 index 000000000..76bedfc30 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/event/DeviceUpdateEvent.java @@ -0,0 +1,22 @@ +package com.genersoft.iot.vmp.jt1078.event; + +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * 设备更新事件 + */ +public class DeviceUpdateEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + public DeviceUpdateEvent(Object source) { + super(source); + } + + @Getter + @Setter + private JTDevice device; +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/event/FtpUploadEvent.java b/src/main/java/com/genersoft/iot/vmp/jt1078/event/FtpUploadEvent.java new file mode 100644 index 000000000..51c4d4327 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/event/FtpUploadEvent.java @@ -0,0 +1,17 @@ +package com.genersoft.iot.vmp.jt1078.event; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +@Setter +@Getter +public class FtpUploadEvent extends ApplicationEvent { + + public FtpUploadEvent(Object source) { + super(source); + } + + private String fileName; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/event/JTPositionEvent.java b/src/main/java/com/genersoft/iot/vmp/jt1078/event/JTPositionEvent.java new file mode 100755 index 000000000..5e30975aa --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/event/JTPositionEvent.java @@ -0,0 +1,27 @@ +package com.genersoft.iot.vmp.jt1078.event; + +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * 设备更新事件 + */ +public class JTPositionEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + public JTPositionEvent(Object source) { + super(source); + } + + @Getter + @Setter + private String phoneNumber; + + @Getter + @Setter + private JTPositionBaseInfo positionInfo; +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/event/eventListener/ConnectChangeEventListener.java b/src/main/java/com/genersoft/iot/vmp/jt1078/event/eventListener/ConnectChangeEventListener.java new file mode 100644 index 000000000..28fb289b0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/event/eventListener/ConnectChangeEventListener.java @@ -0,0 +1,36 @@ +package com.genersoft.iot.vmp.jt1078.event.eventListener; + +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.event.ConnectChangeEvent; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class ConnectChangeEventListener implements ApplicationListener { + + private final static Logger log = LoggerFactory.getLogger(ConnectChangeEventListener.class); + + @Autowired + private Ijt1078Service service; + + @Override + public void onApplicationEvent(ConnectChangeEvent event) { + if (event.isConnected()) { + log.info("[JT-设备已连接] 终端ID: {}", event.getPhoneNumber()); + }else{ + log.info("[JT-设备连接已断开] 终端ID: {}", event.getPhoneNumber()); + if(SessionManager.INSTANCE.get(event.getPhoneNumber()) != null) { + SessionManager.INSTANCE.get(event.getPhoneNumber()).unregister(); + } + } + JTDevice device = service.getDevice(event.getPhoneNumber()); + if (device != null) { + service.updateDeviceStatus(event.isConnected(), event.getPhoneNumber()); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java index 86c5fff26..fdf8080ef 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java @@ -1,12 +1,14 @@ package com.genersoft.iot.vmp.jt1078.proc; import com.genersoft.iot.vmp.jt1078.util.Bin; +import lombok.Data; /** * @author QingtaiJiang * @date 2023/4/27 18:22 * @email qingtaij@163.com */ +@Data public class Header { // 消息ID String msgId; @@ -14,8 +16,8 @@ public class Header { // 消息体属性 Integer msgPro; - // 标识 - String devId; + // 终端手机号 + String phoneNumber; // 消息体流水号 Integer sn; @@ -24,46 +26,6 @@ public class Header { Short version = -1; - public String getMsgId() { - return msgId; - } - - public void setMsgId(String msgId) { - this.msgId = msgId; - } - - public Integer getMsgPro() { - return msgPro; - } - - public void setMsgPro(Integer msgPro) { - this.msgPro = msgPro; - } - - public String getDevId() { - return devId; - } - - public void setDevId(String devId) { - this.devId = devId; - } - - public Integer getSn() { - return sn; - } - - public void setSn(Integer sn) { - this.sn = sn; - } - - public Short getVersion() { - return version; - } - - public void setVersion(Short version) { - this.version = version; - } - /** * 判断是否是2019的版本 * @@ -73,4 +35,14 @@ public class Header { return Bin.get(msgPro, 14); } + @Override + public String toString() { + return "Header{" + + "消息ID='" + msgId + '\'' + + ", 消息体属性=" + msgPro + + ", 终端手机号='" + phoneNumber + '\'' + + ", 消息体流水号=" + sn + + ", 协议版本号=" + version + + '}'; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java index 28726e83b..d6c3a8e90 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java @@ -1,14 +1,16 @@ package com.genersoft.iot.vmp.jt1078.proc.entity; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import lombok.Data; /** * @author QingtaiJiang * @date 2023/4/27 18:23 * @email qingtaij@163.com */ +@Data public class Cmd { - String devId; + String phoneNumber; Long packageNo; String msgId; String respId; @@ -18,62 +20,22 @@ public class Cmd { } public Cmd(Builder builder) { - this.devId = builder.devId; + this.phoneNumber = builder.phoneNumber; this.packageNo = builder.packageNo; this.msgId = builder.msgId; this.respId = builder.respId; this.rs = builder.rs; } - public String getDevId() { - return devId; - } - - public void setDevId(String devId) { - this.devId = devId; - } - - public Long getPackageNo() { - return packageNo; - } - - public void setPackageNo(Long packageNo) { - this.packageNo = packageNo; - } - - public String getMsgId() { - return msgId; - } - - public void setMsgId(String msgId) { - this.msgId = msgId; - } - - public String getRespId() { - return respId; - } - - public void setRespId(String respId) { - this.respId = respId; - } - - public Rs getRs() { - return rs; - } - - public void setRs(Rs rs) { - this.rs = rs; - } - public static class Builder { - String devId; + String phoneNumber; Long packageNo; String msgId; String respId; Rs rs; - public Builder setDevId(String devId) { - this.devId = devId.replaceFirst("^0*", ""); + public Builder setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber.replaceFirst("^0*", ""); return this; } @@ -106,7 +68,7 @@ public class Cmd { @Override public String toString() { return "Cmd{" + - "devId='" + devId + '\'' + + "devId='" + phoneNumber + '\'' + ", packageNo=" + packageNo + ", msgId='" + msgId + '\'' + ", respId='" + respId + '\'' + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java index 1d7f85db0..c775e1583 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java @@ -1,13 +1,15 @@ package com.genersoft.iot.vmp.jt1078.proc.request; -import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; /** * 终端通用应答 @@ -16,10 +18,14 @@ import io.netty.buffer.ByteBufUtil; * @date 2023/4/27 18:04 * @email qingtaij@163.com */ +@Getter @MsgId(id = "0001") public class J0001 extends Re { int respNo; String respId; + /** + * 0:成功/确认;1:失败;2:消息有误;3:不支持 + */ int result; @Override @@ -31,20 +37,13 @@ public class J0001 extends Re { } @Override - protected Rs handler(Header header, Session session) { - SessionManager.INSTANCE.response(header.getDevId(), "0001", (long) respNo, JSON.toJSONString(this)); + protected Rs handler(Header header, Session session, Ijt1078Service service) { + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); return null; } - public int getRespNo() { - return respNo; - } - - public String getRespId() { - return respId; - } - - public int getResult() { - return result; + @Override + public ApplicationEvent getEvent() { + return null; } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java index f52303a6f..71a7523bf 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java @@ -4,8 +4,11 @@ import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; /** * 终端心跳 @@ -14,6 +17,7 @@ import io.netty.buffer.ByteBuf; * @date 2023/4/27 18:04 * @email qingtaij@163.com */ +@Slf4j @MsgId(id = "0002") public class J0002 extends Re { @Override @@ -22,11 +26,17 @@ public class J0002 extends Re { } @Override - protected Rs handler(Header header, Session session) { + protected Rs handler(Header header, Session session, Ijt1078Service service) { + log.info("[终端心跳] {}", header.getPhoneNumber()); J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); return j8001; } + + @Override + public ApplicationEvent getEvent() { + return null; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0003.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0003.java new file mode 100644 index 000000000..f32a4cb05 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0003.java @@ -0,0 +1,46 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +/** + * 终端注销 + */ +@Slf4j +@Getter +@MsgId(id = "0003") +public class J0003 extends Re { + + int respNo; + String respId; + int result; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + respId = ByteBufUtil.hexDump(buf.readSlice(2)); + result = buf.readUnsignedByte(); + log.info("[JT-注销] 设备: {}", header.getPhoneNumber()); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); + return null; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java index 0f00a8013..3db25ce01 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java @@ -3,8 +3,10 @@ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; +import org.springframework.context.ApplicationEvent; /** * 查询服务器时间 @@ -21,7 +23,12 @@ public class J0004 extends Re { } @Override - protected Rs handler(Header header, Session session) { + protected Rs handler(Header header, Session session, Ijt1078Service service) { + return null; + } + + @Override + public ApplicationEvent getEvent() { return null; } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java index a731dda63..8be3abc4a 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java @@ -1,11 +1,23 @@ package com.genersoft.iot.vmp.jt1078.proc.request; +import com.genersoft.iot.vmp.common.CivilCodePo; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8100; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.utils.CivilCodeUtil; +import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.UUID; /** * 终端注册 @@ -14,43 +26,107 @@ import io.netty.buffer.ByteBuf; * @date 2023/4/27 18:06 * @email qingtaij@163.com */ +@Slf4j @MsgId(id = "0100") public class J0100 extends Re { - private int provinceId; - - private int cityId; - - private String makerId; - - private String deviceModel; - - private String deviceId; - - private int plateColor; - - private String plateNo; + private JTDevice device; + private JTDevice deviceForUpdate; @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { Short version = header.getVersion(); - provinceId = buf.readUnsignedShort(); - if (version > 1) { - cityId = buf.readUnsignedShort(); + device = new JTDevice(); + device.setProvinceId(buf.readUnsignedShort() + ""); + if (version >= 1) { + device.setCityId(buf.readUnsignedShort() + ""); // decode as 2019 + device.setMakerId(buf.readCharSequence(11, Charset.forName("GBK")) + .toString().trim()); + + device.setModel(buf.readCharSequence(30, Charset.forName("GBK")) + .toString().trim()); + + device.setTerminalId(buf.readCharSequence(30, Charset.forName("GBK")) + .toString().trim()); + + device.setPlateColor(buf.readByte()); + device.setPlateNo(buf.readCharSequence(buf.readableBytes(), Charset.forName("GBK")) + .toString().trim()); } else { - int i = buf.readUnsignedShort(); // decode as 2013 + device.setCityId(buf.readUnsignedShort() + ""); + // decode as 2019 + byte[] bytes5 = new byte[5]; + buf.readBytes(bytes5); + device.setMakerId(new String(bytes5).trim()); + + byte[] bytes20 = new byte[20]; + buf.readBytes(bytes20); + device.setModel(new String(bytes20).trim()); + + byte[] bytes7 = new byte[7]; + buf.readBytes(bytes7); + device.setTerminalId(new String(bytes7).trim()); + + device.setPlateColor(buf.readByte()); + byte[] plateColorBytes = new byte[buf.readableBytes()]; + buf.readBytes(plateColorBytes); + try { + device.setPlateNo(new String(plateColorBytes, "GBK").trim()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } } return null; } @Override - protected Rs handler(Header header, Session session) { + protected Rs handler(Header header, Session session, Ijt1078Service service) { J8100 j8100 = new J8100(); j8100.setRespNo(header.getSn()); - j8100.setResult(J8100.SUCCESS); - j8100.setCode("WVP_YYDS"); + // 从数据库判断这个设备是否合法 + deviceForUpdate = service.getDevice(header.getPhoneNumber()); + if (deviceForUpdate != null) { + j8100.setResult(J8100.SUCCESS); + String authenticationCode = UUID.randomUUID().toString(); + j8100.setCode(authenticationCode); + session.setAuthenticationCode(authenticationCode); + deviceForUpdate.setStatus(true); + deviceForUpdate.setProvinceId(device.getProvinceId()); + deviceForUpdate.setRegisterTime(DateUtil.getNow()); + CivilCodePo provinceCivilCodePo = CivilCodeUtil.INSTANCE.get(device.getProvinceId()); + if (provinceCivilCodePo != null) { + deviceForUpdate.setProvinceText(provinceCivilCodePo.getName()); + } + deviceForUpdate.setCityId(device.getCityId()); + CivilCodePo cityCivilCodePo = CivilCodeUtil.INSTANCE.get(device.getProvinceId() + + String.format("%04d", Integer.parseInt(device.getCityId()))); + if (cityCivilCodePo != null) { + deviceForUpdate.setCityText(cityCivilCodePo.getName()); + } + deviceForUpdate.setModel(device.getModel()); + deviceForUpdate.setMakerId(device.getMakerId()); + deviceForUpdate.setTerminalId(device.getTerminalId()); + // TODO 支持直接展示车牌颜色的描述 + deviceForUpdate.setPlateColor(device.getPlateColor()); + deviceForUpdate.setPlateNo(device.getPlateNo()); + log.info("[JT-注册成功] 设备: {}", deviceForUpdate); + }else { + log.info("[JT-注册失败] 未授权设备: {}", header.getPhoneNumber()); + j8100.setResult(J8100.FAIL); + // 断开连接,清理资源 + if (session.isRegistered()) { + session.unregister(); + } + } return j8100; } + + @Override + public ApplicationEvent getEvent() { + DeviceUpdateEvent registerEvent = new DeviceUpdateEvent(this); + registerEvent.setDevice(deviceForUpdate); + return registerEvent; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java index 8e531ae41..8d3e2a77b 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java @@ -1,11 +1,18 @@ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +import java.nio.charset.Charset; /** * 终端鉴权 @@ -14,23 +21,50 @@ import io.netty.buffer.ByteBuf; * @date 2023/4/27 18:06 * @email qingtaij@163.com */ +@Slf4j @MsgId(id = "0102") public class J0102 extends Re { + + private String authenticationCode; + private JTDevice deviceForUpdate; + @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { - int lenCode = buf.readUnsignedByte(); -// String code = buf.readCharSequence(lenCode, CharsetUtil.UTF_8).toString(); - // if 2019 to decode next + if (header.is2019Version()) { + int lenCode = buf.readUnsignedByte(); + authenticationCode = buf.readCharSequence(lenCode, Charset.forName("GBK")).toString(); + }else { + authenticationCode = buf.readCharSequence(buf.readableBytes(), Charset.forName("GBK")).toString(); + } + log.info("设备鉴权: authenticationCode: " + authenticationCode); return null; } @Override - protected Rs handler(Header header, Session session) { + protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); - j8001.setResult(J8001.SUCCESS); + if (session.getAuthenticationCode() == null || + !session.getAuthenticationCode().equals(authenticationCode)) { + j8001.setResult(J8001.FAIL); + }else { + j8001.setResult(J8001.SUCCESS); + JTDevice device = service.getDevice(header.getPhoneNumber()); + if (device != null && !device.isStatus()) { + deviceForUpdate = device; + deviceForUpdate.setStatus(true); + service.updateDevice(device); + } + } return j8001; } + @Override + public ApplicationEvent getEvent() { + DeviceUpdateEvent registerEvent = new DeviceUpdateEvent(this); + registerEvent.setDevice(deviceForUpdate); + return registerEvent; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0104.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0104.java new file mode 100644 index 000000000..fbd8ddade --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0104.java @@ -0,0 +1,191 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTAlarmSign; +import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConfig; +import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; +import com.genersoft.iot.vmp.jt1078.bean.config.*; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationEvent; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * 查询终端参数应答 + * + */ +@MsgId(id = "0104") +public class J0104 extends Re { + + Integer respNo; + Integer paramLength; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + paramLength = (int) buf.readUnsignedByte(); + if (paramLength <= 0) { + return null; + } + JTDeviceConfig deviceConfig = new JTDeviceConfig(); + Field[] fields = deviceConfig.getClass().getDeclaredFields(); + Map allFieldMap = new HashMap<>(); + Map allConfigAttributeMap = new HashMap<>(); + for (Field field : fields) { + ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); + if (configAttribute != null) { + allFieldMap.put(configAttribute.id(), field); + allConfigAttributeMap.put(configAttribute.id(), configAttribute); + } + } + for (int i = 0; i < paramLength; i++) { + long id = buf.readUnsignedInt(); + if (!allFieldMap.containsKey(id)) { + continue; + } + short length = buf.readUnsignedByte(); + Field field = allFieldMap.get(id); + try { + + switch (allConfigAttributeMap.get(id).type()) { + case "Long": + Method methodForLong = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Long.class); + methodForLong.invoke(deviceConfig, buf.readUnsignedInt()); + continue; + case "String": + Method methodForString = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), String.class); + String val = buf.readCharSequence(length, Charset.forName("GBK")).toString().trim(); + methodForString.invoke(deviceConfig, val); + continue; + case "Integer": + Method methodForInteger = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Integer.class); + methodForInteger.invoke(deviceConfig, buf.readUnsignedShort()); + continue; + case "Short": + Method methodForShort = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), Short.class); + methodForShort.invoke(deviceConfig, buf.readUnsignedByte()); + continue; + case "IllegalDrivingPeriods": + JTIllegalDrivingPeriods illegalDrivingPeriods = new JTIllegalDrivingPeriods(); + int startHour = buf.readUnsignedByte(); + int startMinute = buf.readUnsignedByte(); + int stopHour = buf.readUnsignedByte(); + int stopMinute = buf.readUnsignedByte(); + illegalDrivingPeriods.setStartTime(startHour + ":" + startMinute); + illegalDrivingPeriods.setEndTime(stopHour + ":" + stopMinute); + Method methodForIllegalDrivingPeriods = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTIllegalDrivingPeriods.class); + methodForIllegalDrivingPeriods.invoke(deviceConfig, illegalDrivingPeriods); + continue; + case "CollisionAlarmParams": + JTCollisionAlarmParams collisionAlarmParams = new JTCollisionAlarmParams(); + collisionAlarmParams.setCollisionAlarmTime(buf.readUnsignedByte()); + collisionAlarmParams.setCollisionAcceleration(buf.readUnsignedByte()); + Method methodForCollisionAlarmParams = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTCollisionAlarmParams.class); + methodForCollisionAlarmParams.invoke(deviceConfig, collisionAlarmParams); + continue; + case "CameraTimer": + JTCameraTimer cameraTimer = new JTCameraTimer(); + long cameraTimerContent = buf.readUnsignedInt(); + cameraTimer.setSwitchForChannel1((cameraTimerContent & 1) == 1); + cameraTimer.setSwitchForChannel2((cameraTimerContent >>> 1 & 1) == 1); + cameraTimer.setSwitchForChannel3((cameraTimerContent >>> 2 & 1) == 1); + cameraTimer.setSwitchForChannel4((cameraTimerContent >>> 3 & 1) == 1); + cameraTimer.setSwitchForChannel5((cameraTimerContent >>> 4 & 1) == 1); + cameraTimer.setStorageFlagsForChannel1((cameraTimerContent >>> 7 & 1) == 1); + cameraTimer.setStorageFlagsForChannel2((cameraTimerContent >>> 8 & 1) == 1); + cameraTimer.setStorageFlagsForChannel3((cameraTimerContent >>> 9 & 1) == 1); + cameraTimer.setStorageFlagsForChannel4((cameraTimerContent >>> 10 & 1) == 1); + cameraTimer.setStorageFlagsForChannel5((cameraTimerContent >>> 11 & 1) == 1); + cameraTimer.setTimeUnit((cameraTimerContent >>> 15 & 1) == 1); + cameraTimer.setTimeInterval((int)cameraTimerContent >>> 16); + Method methodForCameraTimer = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTCameraTimer.class); + methodForCameraTimer.invoke(deviceConfig, cameraTimer); + continue; + case "GnssPositioningMode": + JTGnssPositioningMode gnssPositioningMode = new JTGnssPositioningMode(); + short gnssPositioningModeContent = buf.readUnsignedByte(); + gnssPositioningMode.setGps((gnssPositioningModeContent& 1) == 1); + gnssPositioningMode.setBeidou((gnssPositioningModeContent >>> 1 & 1) == 1); + gnssPositioningMode.setGlonass((gnssPositioningModeContent >>> 2 & 1) == 1); + gnssPositioningMode.setGaLiLeo((gnssPositioningModeContent >>> 3 & 1) == 1); + Method methodForGnssPositioningMode = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTGnssPositioningMode.class); + methodForGnssPositioningMode.invoke(deviceConfig, gnssPositioningMode); + continue; + case "VideoParam": + JTVideoParam videoParam = JTVideoParam.decode(buf); + Method methodForVideoParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTVideoParam.class); + methodForVideoParam.invoke(deviceConfig, videoParam); + continue; + case "ChannelListParam": + JTChannelListParam channelListParam = JTChannelListParam.decode(buf); + Method methodForChannelListParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTChannelListParam.class); + methodForChannelListParam.invoke(deviceConfig, channelListParam); + continue; + case "ChannelParam": + JTChannelParam channelParam = JTChannelParam.decode(buf); + Method methodForChannelParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTChannelParam.class); + methodForChannelParam.invoke(deviceConfig, channelParam); + continue; + case "AlarmRecordingParam": + JTAlarmRecordingParam alarmRecordingParam = JTAlarmRecordingParam.decode(buf); + Method methodForAlarmRecordingParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAlarmRecordingParam.class); + methodForAlarmRecordingParam.invoke(deviceConfig, alarmRecordingParam); + continue; + case "VideoAlarmBit": + JTVideoAlarmBit videoAlarmBit = JTVideoAlarmBit.decode(buf); + Method methodForVideoAlarmBit = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTVideoAlarmBit.class); + methodForVideoAlarmBit.invoke(deviceConfig, videoAlarmBit); + continue; + case "AnalyzeAlarmParam": + JTAnalyzeAlarmParam analyzeAlarmParam = JTAnalyzeAlarmParam.decode(buf); + Method methodForAnalyzeAlarmParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAnalyzeAlarmParam.class); + methodForAnalyzeAlarmParam.invoke(deviceConfig, analyzeAlarmParam); + continue; + case "AwakenParam": + JTAwakenParam awakenParamParam = JTAwakenParam.decode(buf); + Method methodForAwakenParam = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAwakenParam.class); + methodForAwakenParam.invoke(deviceConfig, awakenParamParam); + continue; + case "AlarmSign": + JTAlarmSign alarmSign = JTAlarmSign.decode(buf); + Method methodForAlarmSign = deviceConfig.getClass().getDeclaredMethod("set" + StringUtils.capitalize(field.getName()), JTAlarmSign.class); + methodForAlarmSign.invoke(deviceConfig, alarmSign); + continue; + default: + System.err.println(field.getGenericType().getTypeName()); + continue; + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0104", (long) respNo, deviceConfig); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0107.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0107.java new file mode 100644 index 000000000..6690b1166 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0107.java @@ -0,0 +1,77 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import com.genersoft.iot.vmp.jt1078.util.BCDUtil; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +import java.nio.charset.Charset; + +/** + * 查询终端属性应答 + * + */ +@Slf4j +@MsgId(id = "0107") +public class J0107 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + + JTDeviceAttribute deviceAttribute = new JTDeviceAttribute(); + + deviceAttribute.setType(JTDeviceType.getInstance(buf.readUnsignedShort())); + + deviceAttribute.setMakerId(buf.readCharSequence(5, Charset.forName("GBK")).toString().trim()); + + deviceAttribute.setDeviceModel(buf.readCharSequence(20, Charset.forName("GBK")).toString().trim()); + if (header.is2019Version()) { + buf.readCharSequence(10, Charset.forName("GBK")); + } + + deviceAttribute.setTerminalId(buf.readCharSequence(7, Charset.forName("GBK")).toString().trim()); + if (header.is2019Version()) { + buf.readCharSequence(23, Charset.forName("GBK")); + } + + byte[] bytes = new byte[10]; + buf.readBytes(bytes); + deviceAttribute.setIccId(BCDUtil.transform(bytes)); + + int hardwareVersionLength = buf.readUnsignedByte(); + deviceAttribute.setHardwareVersion(buf.readCharSequence(hardwareVersionLength, Charset.forName("GBK")).toString().trim()); + + int firmwareVersionLength = buf.readUnsignedByte(); + deviceAttribute.setFirmwareVersion(buf.readCharSequence(firmwareVersionLength, Charset.forName("GBK")).toString().trim()); + + deviceAttribute.setGnssAttribute(JTGnssAttribute.getInstance(buf.readUnsignedByte())); + deviceAttribute.setCommunicationModuleAttribute(JTCommunicationModuleAttribute.getInstance(buf.readUnsignedByte())); + log.info("[查询终端属性应答] {}, {}", header.getPhoneNumber(), deviceAttribute); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0107", null, deviceAttribute); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java index d027dd2e0..f42054027 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java @@ -1,32 +1,105 @@ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.*; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; /** - * 实时消息上报 + * 位置信息汇报 * - * @author QingtaiJiang - * @date 2023/4/27 18:06 - * @email qingtaij@163.com */ +@Slf4j @MsgId(id = "0200") public class J0200 extends Re { + + private JTPositionBaseInfo positionInfo; + @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { + positionInfo = JTPositionBaseInfo.decode(buf); + log.debug("[JT-位置汇报]: phoneNumber={} {}", header.getPhoneNumber(), positionInfo.toSimpleString()); + // 读取附加信息 +// JTPositionAdditionalInfo positionAdditionalInfo = new JTPositionAdditionalInfo(); +// Map additionalMsg = new HashMap<>(); +// getAdditionalMsg(buf, positionAdditionalInfo); +// log.info("[JT-位置汇报]: phoneNumber={} {}", header.getPhoneNumber(), positionInfo.toSimpleString()); return null; } + private void getAdditionalMsg(ByteBuf buf, JTPositionAdditionalInfo additionalInfo) { + + if (buf.isReadable()) { + int msgId = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + ByteBuf byteBuf = buf.readBytes(length); + switch (msgId) { + case 1: + // 里程 + long mileage = byteBuf.readUnsignedInt(); + log.info("[JT-位置汇报]: 里程: {} km", (double)mileage/10); + break; + case 2: + // 油量 + int oil = byteBuf.readUnsignedShort(); + log.info("[JT-位置汇报]: 油量: {} L", (double)oil/10); + break; + case 3: + // 速度 + int speed = byteBuf.readUnsignedShort(); + log.info("[JT-位置汇报]: 速度: {} km/h", (double)speed/10); + break; + case 4: + // 需要人工确认报警事件的 ID + int alarmId = byteBuf.readUnsignedShort(); + log.info("[JT-位置汇报]: 需要人工确认报警事件的 ID: {}", alarmId); + break; + case 5: + byte[] tirePressureBytes = new byte[30]; + // 胎压 + byteBuf.readBytes(tirePressureBytes); + log.info("[JT-位置汇报]: 胎压 {}", tirePressureBytes); + break; + case 6: + // 车厢温度 + short carriageTemperature = byteBuf.readShort(); + log.info("[JT-位置汇报]: 车厢温度 {}摄氏度", carriageTemperature); + break; + case 11: + // 超速报警 + short positionType = byteBuf.readUnsignedByte(); + long positionId = byteBuf.readUnsignedInt(); + log.info("[JT-位置汇报]: 超速报警, 位置类型: {}, 区域或路段 ID: {}", positionType, positionId); + break; + default: + log.info("[JT-位置汇报]: 附加消息ID: {}, 消息长度: {}", msgId, length); + break; + + } + getAdditionalMsg(buf, additionalInfo); + } + } + @Override - protected Rs handler(Header header, Session session) { + protected Rs handler(Header header, Session session, Ijt1078Service service) { J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); j8001.setResult(J8001.SUCCESS); + service.updateDevicePosition(header.getPhoneNumber(), positionInfo.getLongitude(), positionInfo.getLatitude()); return j8001; } + + @Override + public ApplicationEvent getEvent() { + return null; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0201.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0201.java new file mode 100644 index 000000000..100812e52 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0201.java @@ -0,0 +1,61 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; +import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +/** + * 位置信息查询应答 + * + * @author QingtaiJiang + * @date 2023/4/27 18:06 + * @email qingtaij@163.com + */ +@MsgId(id = "0201") +public class J0201 extends Re { + + private final static Logger log = LoggerFactory.getLogger(J0100.class); + private JTPositionBaseInfo positionInfo; + private String phoneNumber; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + phoneNumber = header.getPhoneNumber(); + int respNo = buf.readUnsignedShort(); + positionInfo = JTPositionBaseInfo.decode(buf); + log.info("[JT-位置信息查询应答]: {}", positionInfo); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0201", (long) respNo, positionInfo); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + if (positionInfo == null || phoneNumber == null ) { + return null; + } + JTPositionEvent registerEvent = new JTPositionEvent(this); + registerEvent.setPhoneNumber(phoneNumber); + registerEvent.setPositionInfo(positionInfo); + return registerEvent; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0500.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0500.java new file mode 100644 index 000000000..58af88b0d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0500.java @@ -0,0 +1,58 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +/** + * 车辆控制应答 + * + */ +@Slf4j +@MsgId(id = "0500") +public class J0500 extends Re { + + private JTPositionBaseInfo positionInfo; + private String phoneNumber; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + phoneNumber = header.getPhoneNumber(); + int respNo = buf.readUnsignedShort(); + positionInfo = JTPositionBaseInfo.decode(buf); + log.info("[车辆控制应答] {}", header.getPhoneNumber()); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0500", (long) respNo, positionInfo); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + if (positionInfo == null || phoneNumber == null ) { + return null; + } + JTPositionEvent registerEvent = new JTPositionEvent(this); + registerEvent.setPhoneNumber(phoneNumber); + registerEvent.setPositionInfo(positionInfo); + return registerEvent; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0608.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0608.java new file mode 100644 index 000000000..e366579ce --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0608.java @@ -0,0 +1,100 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 查询区域或线路数据应答 + */ +@Slf4j +@MsgId(id = "0608") +public class J0608 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + int type = buf.readByte(); + long dataLength = buf.readUnsignedInt(); + log.info("[JT-查询区域或线路数据应答]: 类型: {}, 数量: {}", type, dataLength); + List areaOrRoutes = new ArrayList<>(); + if (dataLength == 0) { + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, areaOrRoutes); + return null; + } + switch (type) { + case 1: + buf.readUnsignedByte(); + int areaLengthForCircleArea = buf.readUnsignedByte(); + List jtCircleAreas = new ArrayList<>(); + for (int i = 0; i < areaLengthForCircleArea; i++) { + // 查询圆形区域数据 + JTCircleArea jtCircleArea = JTCircleArea.decode(buf); + jtCircleAreas.add(jtCircleArea); + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtCircleAreas); + break; + case 2: + buf.readUnsignedByte(); + int areaLengthForRectangleArea = buf.readUnsignedByte(); + // 查询矩形区域数据 + List jtRectangleAreas = new ArrayList<>(); + for (int i = 0; i < areaLengthForRectangleArea; i++) { + // 查询圆形区域数据 + JTRectangleArea jtRectangleArea = JTRectangleArea.decode(buf); + jtRectangleAreas.add(jtRectangleArea); + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtRectangleAreas); + break; + case 3: + // 查询多 边形区域数据 + List jtPolygonAreas = new ArrayList<>(); + for (int i = 0; i < dataLength; i++) { + // 查询圆形区域数据 + JTPolygonArea jtRectangleArea = JTPolygonArea.decode(buf); + jtPolygonAreas.add(jtRectangleArea); + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtPolygonAreas); + break; + case 4: + // 查询线路数据 + List jtRoutes = new ArrayList<>(); + for (int i = 0; i < dataLength; i++) { + // 查询圆形区域数据 + JTRoute jtRoute = JTRoute.decode(buf); + jtRoutes.add(jtRoute); + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0608", null, jtRoutes); + break; + default: + break; + } + + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0702.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0702.java new file mode 100644 index 000000000..45ba1701c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0702.java @@ -0,0 +1,45 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDriverInformation; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +/** + * 驾驶员身份信息采集上报 + * + */ +@Slf4j +@MsgId(id = "0702") +public class J0702 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + JTDriverInformation driverInformation = JTDriverInformation.decode(buf, header.is2019Version()); + log.info("[JT-驾驶员身份信息采集上报]: {}", driverInformation.toString()); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0702", null, driverInformation); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0704.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0704.java new file mode 100644 index 000000000..346977a41 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0704.java @@ -0,0 +1,57 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDriverInformation; +import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 定位数据批量上传 + */ +@Slf4j +@MsgId(id = "0704") +public class J0704 extends Re { + + private final List positionBaseInfoList = new ArrayList<>(); + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + int length = buf.readUnsignedShort(); + int type = buf.readUnsignedByte(); + for (int i = 0; i < length; i++) { + int dateLength = buf.readUnsignedShort(); + ByteBuf byteBuf = buf.readBytes(dateLength); + JTPositionBaseInfo positionInfo = JTPositionBaseInfo.decode(byteBuf); + byteBuf.release(); + positionBaseInfoList.add(positionInfo); + } + log.info("[JT-定位数据批量上传]: 共{}条", positionBaseInfoList.size()); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0800.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0800.java new file mode 100644 index 000000000..74ee4f8d3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0800.java @@ -0,0 +1,47 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaEventInfo; +import com.genersoft.iot.vmp.jt1078.bean.JTPositionBaseInfo; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 多媒体事件信息上传 + * + */ +@Slf4j +@MsgId(id = "0800") +public class J0800 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + JTMediaEventInfo mediaEventInfo = JTMediaEventInfo.decode(buf); + log.info("[JT-多媒体事件信息上传]: {}", mediaEventInfo); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0801.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0801.java new file mode 100644 index 000000000..d24579b9d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0801.java @@ -0,0 +1,56 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaEventInfo; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +/** + * 多媒体数据上传 + */ +@Slf4j +@MsgId(id = "0801") +public class J0801 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + JTMediaEventInfo mediaEventInfo = JTMediaEventInfo.decode(buf); + log.info("[JT-多媒体数据上传]: {}", mediaEventInfo); +// try { +// if (mediaEventInfo.getMediaData() != null) { +// File file = new File("./source.jpg"); +// if (file.exists()) { +// file.delete(); +// } +// FileOutputStream fileOutputStream = new FileOutputStream(file); +// fileOutputStream.write(mediaEventInfo.getMediaData()); +// fileOutputStream.flush(); +// fileOutputStream.close(); +// } +// }catch (Exception e) { +// log.error("[JT-多媒体数据上传] 写入文件异常", e); +// } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0801", null, mediaEventInfo); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0802.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0802.java new file mode 100644 index 000000000..ee18d0aa8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0802.java @@ -0,0 +1,58 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaDataInfo; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 存储多媒体数据检索应答 + * + */ +@Slf4j +@MsgId(id = "0802") +public class J0802 extends Re { + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + int respNo = buf.readUnsignedShort(); + int length = buf.readUnsignedShort(); + if (length == 0) { + log.info("[JT-存储多媒体数据检索应答]: {}", length); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0802", (long) respNo, new ArrayList<>()); + return null; + } + List mediaDataInfoList = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + mediaDataInfoList.add(JTMediaDataInfo.decode(buf)); + } + log.info("[JT-存储多媒体数据检索应答]: {}", mediaDataInfoList.size()); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0802", (long) respNo, mediaDataInfoList); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0805.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0805.java new file mode 100644 index 000000000..d0a72ef7f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0805.java @@ -0,0 +1,60 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 摄像头立即拍摄命令应答 + */ +@Setter +@Getter +@MsgId(id = "0805") +public class J0805 extends Re { + + private int respNo; + /** + * 0:成功/确认;1:失败;2:消息有误;3:不支持 + */ + private int result; + + /** + * 表示拍摄成功的多媒体个数 + */ + private List ids = new ArrayList<>(); + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + result = buf.readUnsignedByte(); + if (result == 0) { + int length = buf.readUnsignedShort(); + for (int i = 0; i < length; i++) { + ids.add(buf.readUnsignedInt()); + } + } + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0805", null, ids); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + SessionManager.INSTANCE.response(header.getPhoneNumber(), "0001", (long) respNo, result); + return null; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0900.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0900.java new file mode 100644 index 000000000..9271592a7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0900.java @@ -0,0 +1,55 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * 数据上行透传 + */ +@Setter +@Getter +@MsgId(id = "0900") +public class J0900 extends Re { + + /** + * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 + */ + private Integer type; + + /** + * 透传消息内容 + */ + private byte[] content; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + type = (int)buf.readUnsignedByte(); + byte[] content = new byte[buf.readableBytes()]; + buf.readBytes(content); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return null; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0901.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0901.java new file mode 100644 index 000000000..521c4a38a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0901.java @@ -0,0 +1,53 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * 数据压缩上报 + */ +@Setter +@Getter +@MsgId(id = "0901") +public class J0901 extends Re { + + /** + * 平台 RSA公钥{e ,n}中的 e + */ + private Long e; + + /** + * RSA公钥{e ,n}中的 n + */ + private byte[] n; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + e = buf.readUnsignedInt(); + byte[] content = new byte[buf.readableBytes()]; + buf.readBytes(content); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0A00.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0A00.java new file mode 100644 index 000000000..582691fe0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0A00.java @@ -0,0 +1,54 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +/** + * 终端 RSA公钥 + */ +@Setter +@Getter +@MsgId(id = "0900") +public class J0A00 extends Re { + + /** + * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 + */ + + private Integer type; + + /** + * 透传消息内容 + */ + private byte[] content; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + type = (int)buf.readUnsignedByte(); + byte[] content = new byte[buf.readableBytes()]; + buf.readBytes(content); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + return null; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1003.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1003.java new file mode 100644 index 000000000..570f9045b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1003.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaAttribute; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import org.springframework.context.ApplicationEvent; + +/** + * 终端上传音视频属性 + * + */ +@MsgId(id = "1003") +public class J1003 extends Re { + + JTMediaAttribute mediaAttribute; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + mediaAttribute = JTMediaAttribute.decode(buf); + SessionManager.INSTANCE.response(header.getPhoneNumber(), "1003", null, mediaAttribute); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1005.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1005.java new file mode 100644 index 000000000..20ac8ae25 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1005.java @@ -0,0 +1,46 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaAttribute; +import com.genersoft.iot.vmp.jt1078.bean.JTPassengerNum; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +/** + * 终端上传乘客流量 + * + */ +@Slf4j +@MsgId(id = "1005") +public class J1005 extends Re { + + JTPassengerNum passengerNum; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + passengerNum = JTPassengerNum.decode(buf); + log.info("[终端上传乘客流量] {}", passengerNum); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java index da0b89ec3..bbe33a59b 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java @@ -1,14 +1,18 @@ package com.genersoft.iot.vmp.jt1078.proc.request; -import com.alibaba.fastjson2.JSON; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.J8001; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import com.genersoft.iot.vmp.utils.DateUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; import java.util.ArrayList; import java.util.List; @@ -20,11 +24,14 @@ import java.util.List; * @date 2023/4/28 10:36 * @email qingtaij@163.com */ +@Setter +@Getter @MsgId(id = "1205") public class J1205 extends Re { + Integer respNo; - private List recordList = new ArrayList(); + private List recordList = new ArrayList<>(); @Override protected Rs decode0(ByteBuf buf, Header header, Session session) { @@ -34,23 +41,23 @@ public class J1205 extends Re { for (int i = 0; i < size; i++) { JRecordItem item = new JRecordItem(); item.setChannelId(buf.readUnsignedByte()); - item.setStartTime(ByteBufUtil.hexDump(buf.readSlice(6))); - item.setEndTime(ByteBufUtil.hexDump(buf.readSlice(6))); - item.setWarn(buf.readLong()); + String startTime = ByteBufUtil.hexDump(buf.readSlice(6)); + item.setStartTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(startTime)); + String endTime = ByteBufUtil.hexDump(buf.readSlice(6)); + item.setEndTime(DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(endTime)); + item.setAlarmSign(buf.readLong()); item.setMediaType(buf.readUnsignedByte()); item.setStreamType(buf.readUnsignedByte()); item.setStorageType(buf.readUnsignedByte()); item.setSize(buf.readUnsignedInt()); recordList.add(item); } - return null; } @Override - protected Rs handler(Header header, Session session) { - SessionManager.INSTANCE.response(header.getDevId(), "1205", (long) respNo, JSON.toJSONString(this)); - + protected Rs handler(Header header, Session session, Ijt1078Service service) { + SessionManager.INSTANCE.response(header.getPhoneNumber(), "1205", (long) respNo, recordList); J8001 j8001 = new J8001(); j8001.setRespNo(header.getSn()); j8001.setRespId(header.getMsgId()); @@ -59,22 +66,8 @@ public class J1205 extends Re { } - public Integer getRespNo() { - return respNo; - } - - public void setRespNo(Integer respNo) { - this.respNo = respNo; - } - - public List getRecordList() { - return recordList; - } - - public void setRecordList(List recordList) { - this.recordList = recordList; - } - + @Setter + @Getter public static class JRecordItem { // 逻辑通道号 @@ -87,7 +80,7 @@ public class J1205 extends Re { private String endTime; // 报警标志 - private long warn; + private long alarmSign; // 音视频资源类型 private int mediaType; @@ -101,77 +94,13 @@ public class J1205 extends Re { // 文件大小 private long size; - public int getChannelId() { - return channelId; - } - - public void setChannelId(int channelId) { - this.channelId = channelId; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - - public long getWarn() { - return warn; - } - - public void setWarn(long warn) { - this.warn = warn; - } - - public int getMediaType() { - return mediaType; - } - - public void setMediaType(int mediaType) { - this.mediaType = mediaType; - } - - public int getStreamType() { - return streamType; - } - - public void setStreamType(int streamType) { - this.streamType = streamType; - } - - public int getStorageType() { - return storageType; - } - - public void setStorageType(int storageType) { - this.storageType = storageType; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - @Override public String toString() { return "JRecordItem{" + "channelId=" + channelId + ", startTime='" + startTime + '\'' + ", endTime='" + endTime + '\'' + - ", warn=" + warn + + ", warn=" + alarmSign + ", mediaType=" + mediaType + ", streamType=" + streamType + ", storageType=" + storageType + @@ -187,4 +116,9 @@ public class J1205 extends Re { ", recordList=" + recordList + '}'; } + + @Override + public ApplicationEvent getEvent() { + return null; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1206.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1206.java new file mode 100644 index 000000000..efef1645d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1206.java @@ -0,0 +1,63 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * 文件上传完成通知 + * + */ +@Setter +@Getter +@MsgId(id = "1206") +public class J1206 extends Re { + + Integer respNo; + + // 0:成功; 1:失败 + private int result; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + result = buf.readUnsignedByte(); + return null; + } + + @Override + protected Rs handler(Header header, Session session, Ijt1078Service service) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + + @Override + public String toString() { + return "J1206{" + + "respNo=" + respNo + + ", result=" + result + + '}'; + } + + @Override + public ApplicationEvent getEvent() { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java index f30a1eafa..03a66f497 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java @@ -2,8 +2,12 @@ package com.genersoft.iot.vmp.jt1078.proc.request; import com.genersoft.iot.vmp.jt1078.proc.Header; import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.jt1078.session.Session; import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -17,14 +21,15 @@ public abstract class Re { protected abstract Rs decode0(ByteBuf buf, Header header, Session session); - protected abstract Rs handler(Header header, Session session); + protected abstract Rs handler(Header header, Session session, Ijt1078Service service); - public Rs decode(ByteBuf buf, Header header, Session session) { - if (session != null && !StringUtils.hasLength(session.getDevId())) { - session.register(header.getDevId(), (int) header.getVersion(), header); + public Rs decode(ByteBuf buf, Header header, Session session, Ijt1078Service service) { + if (session != null && !StringUtils.hasLength(session.getPhoneNumber())) { + session.register(header.getPhoneNumber(), (int) header.getVersion(), header); } Rs rs = decode0(buf, header, session); - Rs rsHand = handler(header, session); + buf.release(); + Rs rsHand = handler(header, session, service); if (rs == null && rsHand != null) { rs = rsHand; } else if (rs != null && rsHand != null) { @@ -33,7 +38,8 @@ public abstract class Re { if (rs != null) { rs.setHeader(header); } - return rs; } + + public abstract ApplicationEvent getEvent(); } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java index ec9e31f19..07d3aba10 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java @@ -4,16 +4,25 @@ import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import lombok.Setter; /** * @author QingtaiJiang * @date 2023/4/27 18:48 * @email qingtaij@163.com */ +@Setter @MsgId(id = "8001") public class J8001 extends Rs { + public static final Integer SUCCESS = 0; + public static final Integer FAIL = 1; + + public static final Integer ERROR = 2; + public static final Integer NOT_SUPPORTED = 3; + public static final Integer ALARM_ACK = 3; + Integer respNo; String respId; Integer result; @@ -29,15 +38,4 @@ public class J8001 extends Rs { } - public void setRespNo(Integer respNo) { - this.respNo = respNo; - } - - public void setRespId(String respId) { - this.respId = respId; - } - - public void setResult(Integer result) { - this.result = result; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java index 48a9c95e6..af92c2172 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java @@ -4,15 +4,25 @@ import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import lombok.Setter; /** * @author QingtaiJiang * @date 2023/4/27 18:40 * @email qingtaij@163.com */ +@Setter @MsgId(id = "8100") public class J8100 extends Rs { + /** + * 0 成功 + * 1 车辆已被注册 + * 2 数据库中无该车辆 + * 3 终端已被注册 + * 4 数据库中无该终端 + */ public static final Integer SUCCESS = 0; + public static final Integer FAIL = 4; Integer respNo; Integer result; @@ -27,15 +37,4 @@ public class J8100 extends Rs { return buffer; } - public void setRespNo(Integer respNo) { - this.respNo = respNo; - } - - public void setResult(Integer result) { - this.result = result; - } - - public void setCode(String code) { - this.code = code; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8103.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8103.java new file mode 100644 index 000000000..04db44b82 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8103.java @@ -0,0 +1,127 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConfig; +import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; +import com.genersoft.iot.vmp.jt1078.bean.config.*; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 设置终端参数 + */ +@Getter +@MsgId(id = "8103") +public class J8103 extends Rs { + + private final static Logger log = LoggerFactory.getLogger(J8103.class); + + private JTDeviceConfig config; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + Class configClass = config.getClass(); + Field[] declaredFields = configClass.getDeclaredFields(); + Map fieldConfigAttributeMap = new HashMap<>(); + for (Field field : declaredFields) { + try{ + Method method = configClass.getDeclaredMethod("get" + StringUtils.capitalize(field.getName())); + Object invoke = method.invoke(config); + if (invoke == null) { + continue; + } + ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); + if (configAttribute != null) { + fieldConfigAttributeMap.put(field, configAttribute); + } + }catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + log.error("[设置终端参数 ] 编码失败", e ); + } + + } + buffer.writeByte(fieldConfigAttributeMap.size()); + + if (!fieldConfigAttributeMap.isEmpty()) { + for (Field field : fieldConfigAttributeMap.keySet()) { + try{ + ConfigAttribute configAttribute = fieldConfigAttributeMap.get(field); + buffer.writeInt((int) (configAttribute.id() & 0xffff)); + switch (configAttribute.type()) { + case "Long": + buffer.writeByte(4); + field.setAccessible(true); + long longVal = (long)field.get(config); + buffer.writeInt((int) (longVal & 0xffffffffL)); + continue; + case "String": + field.setAccessible(true); + String stringVal = (String)field.get(config); + buffer.writeByte(stringVal.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(stringVal, Charset.forName("GBK")); + continue; + case "Integer": + buffer.writeByte(2); + field.setAccessible(true); + Integer integerVal = (Integer)field.get(config); + buffer.writeShort((short)(integerVal & 0xffff)); + continue; + case "Short": + buffer.writeByte(1); + field.setAccessible(true); + Short shortVal = (Short)field.get(config); + buffer.writeByte((int) (shortVal & 0xff)); + continue; + case "IllegalDrivingPeriods": + case "CollisionAlarmParams": + case "CameraTimer": + case "GnssPositioningMode": + case "VideoParam": + case "ChannelListParam": + case "ChannelParam": + case "AlarmRecordingParam": + case "AlarmShielding": + case "VideoAlarmBit": + case "AnalyzeAlarmParam": + case "AwakenParam": + case "AlarmSign": + field.setAccessible(true); + JTDeviceSubConfig subConfig = (JTDeviceSubConfig)field.get(config); + ByteBuf byteBuf = subConfig.encode(); + buffer.writeByte(byteBuf.readableBytes()); + buffer.writeBytes(byteBuf); + continue; + } + }catch (Exception e) { + log.error("[设置终端参数 ] 编码失败", e ); + } + } + } + return buffer; + } + + public void setConfig(JTDeviceConfig config) { + this.config = config; + } + + @Override + public String toString() { + return "J8103{" + + "config=" + config + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8104.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8104.java new file mode 100644 index 000000000..c4d6012e2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8104.java @@ -0,0 +1,20 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.Arrays; + +/** + * 查询终端参数 + */ +@MsgId(id = "8104") +public class J8104 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8105.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8105.java new file mode 100644 index 000000000..5e590969c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8105.java @@ -0,0 +1,81 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTDeviceConnectionControl; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 终端控制 + */ +@Getter +@MsgId(id = "8105") +public class J8105 extends Rs { + + private JTDeviceConnectionControl connectionControl; + + /** + * 终端复位 + */ + private Boolean reset; + + /** + * 终端恢复出厂设置 + */ + private Boolean factoryReset; + + @Override + public ByteBuf encode() { + ByteBuf byteBuf = Unpooled.buffer(); + if (reset != null) { + byteBuf.writeByte(4); + }else if (factoryReset != null) { + byteBuf.writeByte(5); + }else if (connectionControl != null) { + byteBuf.writeByte(2); + StringBuffer stringBuffer = new StringBuffer(); + if (connectionControl.getSwitchOn() != null) { + if (connectionControl.getSwitchOn()) { + stringBuffer.append("1"); + }else { + stringBuffer.append("0"); + stringBuffer.append(";" + connectionControl.getAuthentication()) + .append(";" + connectionControl.getName()) + .append(";" + connectionControl.getUsername()) + .append(";" + connectionControl.getPassword()) + .append(";" + connectionControl.getAddress()) + .append(";" + connectionControl.getTcpPort()) + .append(";" + connectionControl.getUdpPort()) + .append(";" + connectionControl.getTimeLimit()); + } + } + byteBuf.writeCharSequence(stringBuffer.toString(), Charset.forName("GBK")); + } + return byteBuf; + } + + public void setConnectionControl(JTDeviceConnectionControl connectionControl) { + this.connectionControl = connectionControl; + } + + public void setReset(Boolean reset) { + this.reset = reset; + } + + public void setFactoryReset(Boolean factoryReset) { + this.factoryReset = factoryReset; + } + + @Override + public String toString() { + return "J8105{" + + "connectionControl=" + connectionControl + + ", reset=" + reset + + ", factoryReset=" + factoryReset + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8106.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8106.java new file mode 100644 index 000000000..4c7fe9d3a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8106.java @@ -0,0 +1,40 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 查询指定终端参数 + */ +@Getter +@MsgId(id = "8106") +public class J8106 extends Rs { + + private long[] params; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(params.length); + for (long param : params) { + buffer.writeInt((int) param); + } + return buffer; + } + + public void setParams(long[] params) { + this.params = params; + } + + @Override + public String toString() { + return "J8106{" + + "params=" + Arrays.toString(params) + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8107.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8107.java new file mode 100644 index 000000000..a516d2c36 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8107.java @@ -0,0 +1,18 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 查询终端属性 + */ +@MsgId(id = "8107") +public class J8107 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8201.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8201.java new file mode 100644 index 000000000..1d360b045 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8201.java @@ -0,0 +1,18 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 位置信息查询 + */ +@MsgId(id = "8201") +public class J8201 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8202.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8202.java new file mode 100644 index 000000000..1c6ff8a2a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8202.java @@ -0,0 +1,37 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 临时位置跟踪控制 + */ +@Setter +@Getter +@MsgId(id = "8202") +public class J8202 extends Rs { + + /** + * 时间间隔,单位为秒,时间间隔为0 时停止跟踪,停止跟踪无需带后继字段 + */ + private int timeInterval; + + /** + * 位置跟踪有效期, 单位为秒,终端在接收到位置跟踪控制消息后,在有效期截止时间之前依据消息中的时间间隔发送位置汇报 + */ + private long validityPeriod; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort((short)(timeInterval & 0xffff)); + if (timeInterval > 0) { + buffer.writeInt((int) (validityPeriod & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8203.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8203.java new file mode 100644 index 000000000..d537c4ff7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8203.java @@ -0,0 +1,37 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTConfirmationAlarmMessageType; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 人工确认报警消息 + */ +@Setter +@Getter +@MsgId(id = "8203") +public class J8203 extends Rs { + + /** + * 报警消息流水号 + */ + private int alarmPackageNo; + /** + * 人工确认报警类型 + */ + private JTConfirmationAlarmMessageType alarmMessageType; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort((short)(alarmPackageNo & 0xffff)); + if (alarmMessageType != null) { + buffer.writeInt((int) (alarmMessageType.encode() & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8204.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8204.java new file mode 100644 index 000000000..745530128 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8204.java @@ -0,0 +1,18 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 链路检测 + */ +@MsgId(id = "8204") +public class J8204 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8300.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8300.java new file mode 100644 index 000000000..59e3e6ed7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8300.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.nio.charset.Charset; + +/** + * 文本信息下发 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@MsgId(id = "8300") +public class J8300 extends Rs { + + /** + * 标志 + */ + private JTTextSign sign; + + /** + * 文本类型1 = 通知 ,2 = 服务 + */ + private int textType; + + /** + * 文本信息 + */ + private String content; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(sign.encode()); + buffer.writeByte(textType); + buffer.writeCharSequence(content, Charset.forName("GBK")); + return buffer; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8400.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8400.java new file mode 100644 index 000000000..8779118e3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8400.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTTextSign; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.nio.charset.Charset; + +/** + * 电话回拨 + */ +@Setter +@Getter +@MsgId(id = "8400") +public class J8400 extends Rs { + + /** + * 标志, 0'普通通话,1'监听 + */ + private int sign; + + /** + * 电话号码 + */ + private String phoneNumber; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(sign); + buffer.writeCharSequence(phoneNumber, Charset.forName("GBK")); + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8401.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8401.java new file mode 100644 index 000000000..960fe5e12 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8401.java @@ -0,0 +1,50 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTPhoneBookContact; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.nio.charset.Charset; +import java.util.List; + +/** + * 设置电话本 + */ +@Setter +@Getter +@MsgId(id = "8401") +public class J8401 extends Rs { + + /** + * 设置类型: + * 0: 删除终端上所有存储的联系人, + * 1: 表示更新电话本$ 删除终端中已有全部联系人并追加消 息中的联系人, + * 2: 表示追加电话本, + * 3: 表示修改电话本$以联系人为索引 + */ + private int type; + + /** + * 联系人 + */ + private List phoneBookContactList; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(type); + if (phoneBookContactList != null && !phoneBookContactList.isEmpty()) { + buffer.writeByte(phoneBookContactList.size()); + for (JTPhoneBookContact jtPhoneBookContact : phoneBookContactList) { + buffer.writeBytes(jtPhoneBookContact.encode()); + } + }else { + buffer.writeByte(0); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8500.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8500.java new file mode 100644 index 000000000..047ed8bfb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8500.java @@ -0,0 +1,67 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTPhoneBookContact; +import com.genersoft.iot.vmp.jt1078.bean.JTVehicleControl; +import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; +import com.genersoft.iot.vmp.jt1078.bean.config.JTDeviceSubConfig; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 车辆控制 + */ +@Setter +@Getter +@MsgId(id = "8500") +public class J8500 extends Rs { + + /** + * 控制类型 + */ + private JTVehicleControl vehicleControl; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort((short)(vehicleControl.getLength() & 0xffff)); + + Field[] fields = vehicleControl.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + Object value = null; + try { + value = field.get(vehicleControl); + if (value == null) { + continue; + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); + if (configAttribute == null) { + continue; + } + buffer.writeShort((short)(configAttribute.id() & 0xffff)); + switch (configAttribute.type()) { + case "Byte": + field.setAccessible(true); + buffer.writeByte((int)value); + continue; + } + } + + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8600.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8600.java new file mode 100644 index 000000000..8fc9063b7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8600.java @@ -0,0 +1,48 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; +import com.genersoft.iot.vmp.jt1078.bean.JTVehicleControl; +import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * 设置圆形区域 + */ +@Setter +@Getter +@MsgId(id = "8600") +public class J8600 extends Rs { + + /** + * 设置属性, 0:更新区域; 1:追加区域; 2:修改区域 + */ + private int attribute; + + /** + * 区域项 + */ + private List circleAreaList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(attribute); + buffer.writeByte(circleAreaList.size()); + if (circleAreaList.isEmpty()) { + return buffer; + } + for (JTCircleArea circleArea : circleAreaList) { + buffer.writeBytes(circleArea.encode()); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8601.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8601.java new file mode 100644 index 000000000..2d0a7f008 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8601.java @@ -0,0 +1,42 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 删除圆形区域 + */ +@Setter +@Getter +@MsgId(id = "8601") +public class J8601 extends Rs { + + + /** + * 待删除的区域ID + */ + private List idList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + if (idList == null || idList.isEmpty()) { + buffer.writeByte(0); + return buffer; + }else { + buffer.writeByte(idList.size()); + } + for (Long id : idList) { + buffer.writeInt((int) (id & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8602.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8602.java new file mode 100644 index 000000000..bda25b157 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8602.java @@ -0,0 +1,46 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTCircleArea; +import com.genersoft.iot.vmp.jt1078.bean.JTRectangleArea; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 设置矩形区域 + */ +@Setter +@Getter +@MsgId(id = "8602") +public class J8602 extends Rs { + + /** + * 设置属性, 0:更新区域; 1:追加区域; 2:修改区域 + */ + private int attribute; + + /** + * 区域项 + */ + private List rectangleAreas; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(attribute); + buffer.writeByte(rectangleAreas.size()); + if (rectangleAreas.isEmpty()) { + return buffer; + } + for (JTRectangleArea area : rectangleAreas) { + buffer.writeBytes(area.encode()); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8603.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8603.java new file mode 100644 index 000000000..2b5dbbf27 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8603.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 删除矩形区域 + */ +@Setter +@Getter +@MsgId(id = "8603") +public class J8603 extends Rs { + + + /** + * 待删除的区域ID + */ + private List idList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + if (idList == null || idList.isEmpty()) { + buffer.writeByte(0); + return buffer; + }else { + buffer.writeByte(idList.size()); + } + for (Long id : idList) { + buffer.writeInt((int) (id & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8604.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8604.java new file mode 100644 index 000000000..2e54dedc9 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8604.java @@ -0,0 +1,34 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTPolygonArea; +import com.genersoft.iot.vmp.jt1078.bean.JTRectangleArea; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 设置多边形区域 + */ +@Setter +@Getter +@MsgId(id = "8604") +public class J8604 extends Rs { + + /** + * 多边形区域 + */ + private JTPolygonArea polygonArea; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeBytes(polygonArea.encode()); + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8605.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8605.java new file mode 100644 index 000000000..492cf6a32 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8605.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 删除多边形区域 + */ +@Setter +@Getter +@MsgId(id = "8605") +public class J8605 extends Rs { + + + /** + * 待删除的区域ID + */ + private List idList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + if (idList == null || idList.isEmpty()) { + buffer.writeByte(0); + return buffer; + }else { + buffer.writeByte(idList.size()); + } + for (Long id : idList) { + buffer.writeInt((int) (id & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8606.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8606.java new file mode 100644 index 000000000..cfcded138 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8606.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTPolygonArea; +import com.genersoft.iot.vmp.jt1078.bean.JTRoute; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 设置路线 + */ +@Setter +@Getter +@MsgId(id = "8606") +public class J8606 extends Rs { + + /** + * 路线 + */ + private JTRoute route; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeBytes(route.encode()); + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8607.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8607.java new file mode 100644 index 000000000..1b31235f3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8607.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 删除路线 + */ +@Setter +@Getter +@MsgId(id = "8607") +public class J8607 extends Rs { + + + /** + * 待删除的路线ID + */ + private List idList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + if (idList == null || idList.isEmpty()) { + buffer.writeByte(0); + return buffer; + }else { + buffer.writeByte(idList.size()); + } + for (Long id : idList) { + buffer.writeInt((int) (id & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8608.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8608.java new file mode 100644 index 000000000..5a33918ed --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8608.java @@ -0,0 +1,48 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 查询区域或线路数据 + */ +@Setter +@Getter +@MsgId(id = "8608") +public class J8608 extends Rs { + + + /** + * 查询类型, 1 = 查询圆形区域数据 ,2 = 查询矩形区域数据 ,3 = 查询多 边形区域数据 ,4 = 查询线路数据 + */ + private int type; + + + /** + * 要查询的区域或线路的 ID + */ + private List idList; + + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(type); + if (idList == null || idList.isEmpty()) { + buffer.writeInt(0); + return buffer; + }else { + buffer.writeInt(idList.size()); + } + for (Long id : idList) { + buffer.writeInt((int) (id & 0xffffffffL)); + } + return buffer; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8702.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8702.java new file mode 100644 index 000000000..ba457cde3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8702.java @@ -0,0 +1,18 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 上报驾驶员身份信息请求 + */ +@MsgId(id = "8702") +public class J8702 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8801.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8801.java new file mode 100644 index 000000000..83d7e4fb2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8801.java @@ -0,0 +1,28 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 摄像头立即拍摄命令 + */ +@Setter +@Getter +@MsgId(id = "8801") +public class J8801 extends Rs { + + JTShootingCommand command; + + @Override + public ByteBuf encode() { + return command.decode(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8802.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8802.java new file mode 100644 index 000000000..f7492c1c1 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8802.java @@ -0,0 +1,25 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; +import com.genersoft.iot.vmp.jt1078.bean.JTShootingCommand; +import io.netty.buffer.ByteBuf; +import lombok.Getter; +import lombok.Setter; + +/** + * 存储多媒体数据检索 + */ +@Setter +@Getter +@MsgId(id = "8802") +public class J8802 extends Rs { + + JTQueryMediaDataCommand command; + + @Override + public ByteBuf encode() { + return command.decode(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8803.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8803.java new file mode 100644 index 000000000..59cf091b0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8803.java @@ -0,0 +1,24 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; +import io.netty.buffer.ByteBuf; +import lombok.Getter; +import lombok.Setter; + +/** + * 存储多媒体数据上传命令 + */ +@Setter +@Getter +@MsgId(id = "8803") +public class J8803 extends Rs { + + JTQueryMediaDataCommand command; + + @Override + public ByteBuf encode() { + return command.decode(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8804.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8804.java new file mode 100644 index 000000000..2414d68a4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8804.java @@ -0,0 +1,48 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 录音开始/停止命令 + */ +@Setter +@Getter +@MsgId(id = "8804") +public class J8804 extends Rs { + + /** + * 录音命令, 0:停止录音;0X01:开始录音 + */ + private int commond; + + /** + * 录音时长,单位为秒(s) ,0 表示一直录音 + */ + private int duration; + + /** + * 保存标志, 0:实时上传;1:保存 + */ + private int save; + + /** + * 音频采样率, 0:8K;1:11K;2:23K;3:32K;其他保留 + */ + private int samplingRate; + + @Override + public ByteBuf encode() { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeByte(commond); + byteBuf.writeShort((short)(duration & 0xffff)); + byteBuf.writeByte(save); + byteBuf.writeByte(samplingRate); + return byteBuf; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8805.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8805.java new file mode 100644 index 000000000..c64db8851 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8805.java @@ -0,0 +1,37 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.bean.JTQueryMediaDataCommand; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + * 单条存储多媒体数据检索上传命令 + */ +@Setter +@Getter +@MsgId(id = "8805") +public class J8805 extends Rs { + + /** + * 多媒体 ID + */ + private Long mediaId; + + /** + * 删除标志, 0:保留;1:删除, 存储多媒体数据上传命令中使用 + */ + private Integer delete; + + @Override + public ByteBuf encode() { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeInt((int) (mediaId & 0xffffffffL)); + byteBuf.writeByte(delete); + return byteBuf; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8900.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8900.java new file mode 100644 index 000000000..b8ef60f43 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8900.java @@ -0,0 +1,35 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 数据下行透传 + */ +@Setter +@Getter +@MsgId(id = "8900") +public class J8900 extends Rs { + + /** + * 透传消息类型, 0x00: GNSS 模块详细定位数据, 0X0B: 道路运输证 IC卡信息, 0X41: 串口1 透传, 0X42: 串口2 透传, 0XF0 ~ 0XFF: 用户自定义透传 + */ + private Integer type; + + /** + * 透传消息内容 + */ + private byte[] content; + + @Override + public ByteBuf encode() { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeByte(type); + byteBuf.writeBytes(content); + return byteBuf; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8A00.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8A00.java new file mode 100644 index 000000000..51f3df59f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8A00.java @@ -0,0 +1,35 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 平台 RSA公钥 + */ +@Setter +@Getter +@MsgId(id = "8A00") +public class J8A00 extends Rs { + + /** + * 平台 RSA公钥{e ,n}中的 e + */ + private Long e; + + /** + * RSA公钥{e ,n}中的 n + */ + private byte[] n; + + @Override + public ByteBuf encode() { + ByteBuf byteBuf = Unpooled.buffer(); + byteBuf.writeInt((int) (e & 0xffffffffL)); + byteBuf.writeBytes(n); + return byteBuf; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9003.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9003.java new file mode 100644 index 000000000..9bbd9d070 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9003.java @@ -0,0 +1,18 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 查询终端音视频属性 + */ +@MsgId(id = "9003") +public class J9003 extends Rs { + + @Override + public ByteBuf encode() { + return Unpooled.buffer(); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java index 77e90b726..935021a9f 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java @@ -4,6 +4,10 @@ import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import lombok.Getter; +import lombok.Setter; + +import java.nio.charset.Charset; /** * 实时音视频传输请求 @@ -12,6 +16,8 @@ import io.netty.util.CharsetUtil; * @date 2023/4/27 18:25 * @email qingtaij@163.com */ +@Setter +@Getter @MsgId(id = "9101") public class J9101 extends Rs { String ip; @@ -40,8 +46,8 @@ public class J9101 extends Rs { @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); - buffer.writeByte(ip.getBytes().length); - buffer.writeCharSequence(ip, CharsetUtil.UTF_8); + buffer.writeByte(ip.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(ip, Charset.forName("GBK")); buffer.writeShort(tcpPort); buffer.writeShort(udpPort); buffer.writeByte(channel); @@ -50,54 +56,6 @@ public class J9101 extends Rs { return buffer; } - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public Integer getTcpPort() { - return tcpPort; - } - - public void setTcpPort(Integer tcpPort) { - this.tcpPort = tcpPort; - } - - public Integer getUdpPort() { - return udpPort; - } - - public void setUdpPort(Integer udpPort) { - this.udpPort = udpPort; - } - - public Integer getChannel() { - return channel; - } - - public void setChannel(Integer channel) { - this.channel = channel; - } - - public Integer getType() { - return type; - } - - public void setType(Integer type) { - this.type = type; - } - - public Integer getRate() { - return rate; - } - - public void setRate(Integer rate) { - this.rate = rate; - } - @Override public String toString() { return "J9101{" + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java index 8d560b20c..61d7f0cf6 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java @@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.jt1078.proc.response; import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; /** * 音视频实时传输控制 @@ -11,6 +13,8 @@ import io.netty.buffer.Unpooled; * @date 2023/4/27 18:49 * @email qingtaij@163.com */ +@Setter +@Getter @MsgId(id = "9102") public class J9102 extends Rs { @@ -55,38 +59,6 @@ public class J9102 extends Rs { } - public Integer getChannel() { - return channel; - } - - public void setChannel(Integer channel) { - this.channel = channel; - } - - public Integer getCommand() { - return command; - } - - public void setCommand(Integer command) { - this.command = command; - } - - public Integer getCloseType() { - return closeType; - } - - public void setCloseType(Integer closeType) { - this.closeType = closeType; - } - - public Integer getStreamType() { - return streamType; - } - - public void setStreamType(Integer streamType) { - this.streamType = streamType; - } - @Override public String toString() { return "J9102{" + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java index 8a66f3544..3de971172 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java @@ -5,6 +5,10 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import lombok.Getter; +import lombok.Setter; + +import java.nio.charset.Charset; /** * 回放请求 @@ -13,6 +17,8 @@ import io.netty.util.CharsetUtil; * @date 2023/4/28 10:37 * @email qingtaij@163.com */ +@Setter +@Getter @MsgId(id = "9201") public class J9201 extends Rs { // 服务器IP地址 @@ -51,8 +57,8 @@ public class J9201 extends Rs { @Override public ByteBuf encode() { ByteBuf buffer = Unpooled.buffer(); - buffer.writeByte(ip.getBytes().length); - buffer.writeCharSequence(ip, CharsetUtil.UTF_8); + buffer.writeByte(ip.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(ip, Charset.forName("GBK")); buffer.writeShort(tcpPort); buffer.writeShort(udpPort); buffer.writeByte(channel); @@ -66,94 +72,6 @@ public class J9201 extends Rs { return buffer; } - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public int getTcpPort() { - return tcpPort; - } - - public void setTcpPort(int tcpPort) { - this.tcpPort = tcpPort; - } - - public int getUdpPort() { - return udpPort; - } - - public void setUdpPort(int udpPort) { - this.udpPort = udpPort; - } - - public int getChannel() { - return channel; - } - - public void setChannel(int channel) { - this.channel = channel; - } - - public int getType() { - return type; - } - - public void setType(int type) { - this.type = type; - } - - public int getRate() { - return rate; - } - - public void setRate(int rate) { - this.rate = rate; - } - - public int getStorageType() { - return storageType; - } - - public void setStorageType(int storageType) { - this.storageType = storageType; - } - - public int getPlaybackType() { - return playbackType; - } - - public void setPlaybackType(int playbackType) { - this.playbackType = playbackType; - } - - public int getPlaybackSpeed() { - return playbackSpeed; - } - - public void setPlaybackSpeed(int playbackSpeed) { - this.playbackSpeed = playbackSpeed; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - @Override public String toString() { return "J9201{" + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java index 7cb4e53ef..805d2dd2d 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java @@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.jt1078.annotation.MsgId; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; /** * 平台下发远程录像回放控制 @@ -12,6 +14,8 @@ import io.netty.buffer.Unpooled; * @date 2023/4/28 10:37 * @email qingtaij@163.com */ +@Setter +@Getter @MsgId(id = "9202") public class J9202 extends Rs { // 逻辑通道号 @@ -32,42 +36,13 @@ public class J9202 extends Rs { buffer.writeByte(channel); buffer.writeByte(playbackType); buffer.writeByte(playbackSpeed); - buffer.writeBytes(ByteBufUtil.decodeHexDump(playbackTime)); + if (playbackType == 5) { + buffer.writeBytes(ByteBufUtil.decodeHexDump(playbackTime)); + } + return buffer; } - public int getChannel() { - return channel; - } - - public void setChannel(int channel) { - this.channel = channel; - } - - public int getPlaybackType() { - return playbackType; - } - - public void setPlaybackType(int playbackType) { - this.playbackType = playbackType; - } - - public int getPlaybackSpeed() { - return playbackSpeed; - } - - public void setPlaybackSpeed(int playbackSpeed) { - this.playbackSpeed = playbackSpeed; - } - - public String getPlaybackTime() { - return playbackTime; - } - - public void setPlaybackTime(String playbackTime) { - this.playbackTime = playbackTime; - } - @Override public String toString() { return "J9202{" + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9206.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9206.java new file mode 100644 index 000000000..fefa084af --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9206.java @@ -0,0 +1,105 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +import java.nio.charset.Charset; + +/** + * 文件上传指令 + * + */ +@Setter +@Getter +@MsgId(id = "9206") +public class J9206 extends Rs { + + // 服务器地址 + private String serverIp; + // 服务器端口 + private int port; + // 用户名 + private String username; + // 密码 + private String password; + // 文件上传路径 + private String path; + // 逻辑通道号 + private int channelId; + + // 开始时间YYMMDDHHMMSS,全0表示无起始时间 + private String startTime; + + // 结束时间YYMMDDHHMMSS,全0表示无终止时间 + private String endTime; + + // 报警标志 + private int alarmSign = 0; + + // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 + private int mediaType; + + // 码流类型:0.所有码流 1.主码流 2.子码流 + private int streamType = 0; + + // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器 + private int storageType = 0; + + // 任务执行条件, + // 1:仅WI-FI 下可下载, + // 2: 仅LAN 连接时可下载; + // 3: WI-FI + LAN 连接时可下载; + // 4: 仅3G/ 4G 连接时可下载 + // 5: WI-FI + 3G/ 4G 连接时可下载 + // 6: WI-FI + LAN 连接时可下载 + // 7: WI-FI + LAN + 3G/ 4G 连接时可下载 + private int taskConditions = 7; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + + buffer.writeByte(serverIp.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(serverIp, Charset.forName("GBK")); + buffer.writeShort(port); + buffer.writeByte(username.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(username, Charset.forName("GBK")); + buffer.writeByte(password.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(password, Charset.forName("GBK")); + buffer.writeByte(path.getBytes(Charset.forName("GBK")).length); + buffer.writeCharSequence(path, Charset.forName("GBK")); + buffer.writeByte(channelId); + buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); + buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); + buffer.writeLong(alarmSign); + buffer.writeByte(mediaType); + buffer.writeByte(streamType); + buffer.writeByte(storageType); + buffer.writeByte(taskConditions); + return buffer; + } + + + @Override + public String toString() { + return "J9206{" + + "serverIp='" + serverIp + '\'' + + ", port=" + port + + ", user='" + username + '\'' + + ", password='" + password + '\'' + + ", path='" + path + '\'' + + ", channelId=" + channelId + + ", startTime='" + startTime + '\'' + + ", endTime='" + endTime + '\'' + + ", warnType=" + alarmSign + + ", mediaType=" + mediaType + + ", streamType=" + streamType + + ", storageType=" + storageType + + ", taskConditions=" + taskConditions + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9207.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9207.java new file mode 100644 index 000000000..d7a3c1780 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9207.java @@ -0,0 +1,42 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import lombok.Getter; +import lombok.Setter; + +/** + * 文件上传控制 + * + */ +@Setter +@Getter +@MsgId(id = "9207") +public class J9207 extends Rs { + + // 对应平台文件上传消息的流水号 + Integer respNo; + + // 控制: 0:暂停; 1:继续; 2:取消 + private int control; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort(respNo); + buffer.writeByte(control); + return buffer; + } + + + @Override + public String toString() { + return "J9207{" + + "respNo=" + respNo + + ", control=" + control + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9301.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9301.java new file mode 100644 index 000000000..b7852b878 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9301.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-云台旋转 + * + */ +@Setter +@Getter +@MsgId(id = "9301") +public class J9301 extends Rs { + // 逻辑通道号 + private int channel; + + // 方向: 0:停止; 1:上; 2:下; 3:左; 4:右 + private int direction; + + // 速度:0 ~ 255 + private int speed; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(direction); + buffer.writeByte(speed); + return buffer; + } + + @Override + public String toString() { + return "J9301{" + + "channel=" + channel + + ", direction=" + direction + + ", speed=" + speed + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9302.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9302.java new file mode 100644 index 000000000..457bbf7f6 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9302.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-焦距控制 + * + */ +@Setter +@Getter +@MsgId(id = "9302") +public class J9302 extends Rs { + // 逻辑通道号 + private int channel; + + // 方向: 0:焦距调大; 1:焦距调小 + private int focalDirection; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(focalDirection); + return buffer; + } + + @Override + public String toString() { + return "J9302{" + + "channel=" + channel + + ", zoomDirection=" + focalDirection + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9303.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9303.java new file mode 100644 index 000000000..662ea8c69 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9303.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-光圈控制 + * + */ +@Setter +@Getter +@MsgId(id = "9303") +public class J9303 extends Rs { + // 逻辑通道号 + private int channel; + + // 调整方式: 0:调大; 1:调小 + private int iris; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(iris); + return buffer; + } + + @Override + public String toString() { + return "J9303{" + + "channel=" + channel + + ", iris=" + iris + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9304.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9304.java new file mode 100644 index 000000000..557a9b228 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9304.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-云台雨刷控制 + * + */ +@Setter +@Getter +@MsgId(id = "9304") +public class J9304 extends Rs { + // 逻辑通道号 + private int channel; + + // 启停标识: 0:停止; 1:启动 + private int on; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(on); + return buffer; + } + + @Override + public String toString() { + return "J9304{" + + "channel=" + channel + + ", on=" + on + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9305.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9305.java new file mode 100644 index 000000000..3b34920f3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9305.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-红外补光控制 + * + */ +@Setter +@Getter +@MsgId(id = "9305") +public class J9305 extends Rs { + // 逻辑通道号 + private int channel; + + // 启停标识: 0:停止; 1:启动 + private int on; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(on); + return buffer; + } + + @Override + public String toString() { + return "J9305{" + + "channel=" + channel + + ", on=" + on + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9306.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9306.java new file mode 100644 index 000000000..ff60132b2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9306.java @@ -0,0 +1,38 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; + +/** + * 云台控制指令-云台变倍控制 + * + */ +@Setter +@Getter +@MsgId(id = "9306") +public class J9306 extends Rs { + // 逻辑通道号 + private int channel; + + // 0:调大; 1:调小 + private int zoom; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(zoom); + return buffer; + } + + @Override + public String toString() { + return "J9306{" + + "channel=" + channel + + ", zoom=" + zoom + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078PlayService.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078PlayService.java new file mode 100644 index 000000000..c0f8fd340 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078PlayService.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.jt1078.service; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.proc.request.J1205; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; + +import java.util.List; + +public interface Ijt1078PlayService { + + JTMediaStreamType checkStreamFromJt(String stream); + + void play(String phoneNumber, Integer channelId, int type, CommonCallback> callback); + + void playback(String phoneNumber, Integer channelId, String startTime, String endTime, Integer type, + Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback); + + void stopPlay(String phoneNumber, Integer channelId); + + void pausePlay(String phoneNumber, Integer channelId); + + void continueLivePlay(String phoneNumber, Integer channelId); + + List getRecordList(String phoneNumber, Integer channelId, String startTime, String endTime); + + void stopPlayback(String phoneNumber, Integer channelId); + + StreamInfo startTalk(String phoneNumber, Integer channelId); + + void stopTalk(String phoneNumber, Integer channelId); + + void playbackControl(String phoneNumber, Integer channelId, Integer command, Integer playbackSpeed, String time); + + void start(Integer channelId, Boolean record, ErrorCallback callback); + + void stop(Integer channelId); + + void playBack(Integer channelId, Long startTime, Long stopTime, ErrorCallback callback); +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078Service.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078Service.java new file mode 100644 index 000000000..7ca1b58f8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/Ijt1078Service.java @@ -0,0 +1,130 @@ +package com.genersoft.iot.vmp.jt1078.service; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import com.github.pagehelper.PageInfo; + +import javax.servlet.ServletOutputStream; +import java.io.OutputStream; +import java.util.List; + +public interface Ijt1078Service { + + JTMediaStreamType checkStreamFromJt(String stream); + + JTDevice getDevice(String phoneNumber); + + JTChannel getChannel(Integer terminalDbId, Integer channelId); + + void updateDevice(JTDevice deviceInDb); + + PageInfo getDeviceList(int page, int count, String query, Boolean online); + + void addDevice(JTDevice device); + + void deleteDeviceByPhoneNumber(String phoneNumber); + + void updateDeviceStatus(boolean connected, String phoneNumber); + + + void ptzControl(String phoneNumber, Integer channelId, String command, int speed); + + void supplementaryLight(String phoneNumber, Integer channelId, String command); + + void wiper(String phoneNumber, Integer channelId, String command); + + JTDeviceConfig queryConfig(String phoneNumber, String[] params); + + void setConfig(String phoneNumber, JTDeviceConfig config); + + void connectionControl(String phoneNumber, JTDeviceConnectionControl control); + + void resetControl(String phoneNumber); + + void factoryResetControl(String phoneNumber); + + JTDeviceAttribute attribute(String phoneNumber); + + JTPositionBaseInfo queryPositionInfo(String phoneNumber); + + void tempPositionTrackingControl(String phoneNumber, Integer timeInterval, Long validityPeriod); + + void confirmationAlarmMessage(String phoneNumber, int alarmPackageNo, JTConfirmationAlarmMessageType alarmMessageType); + + int linkDetection(String phoneNumber); + + int textMessage(String phoneNumber,JTTextSign sign, int textType, String content); + + int telephoneCallback(String phoneNumber, Integer sign, String destPhoneNumber); + + int setPhoneBook(String phoneNumber, int type, List phoneBookContactList); + + JTPositionBaseInfo controlDoor(String phoneNumber, Boolean open); + + int setAreaForCircle(int attribute, String phoneNumber, List circleAreaList); + + int deleteAreaForCircle(String phoneNumber, List ids); + + List queryAreaForCircle(String phoneNumber, List ids); + + int setAreaForRectangle(int i, String phoneNumber, List rectangleAreas); + + int deleteAreaForRectangle(String phoneNumber, List ids); + + List queryAreaForRectangle(String phoneNumber, List ids); + + int setAreaForPolygon(String phoneNumber, JTPolygonArea polygonArea); + + int deleteAreaForPolygon(String phoneNumber, List ids); + + List queryAreaForPolygon(String phoneNumber, List ids); + + int setRoute(String phoneNumber, JTRoute route); + + int deleteRoute(String phoneNumber, List ids); + + List queryRoute(String phoneNumber, List ids); + + JTDriverInformation queryDriverInformation(String phoneNumber); + + List shooting(String phoneNumber, JTShootingCommand shootingCommand); + + List queryMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand); + + void uploadMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand); + + void record(String phoneNumber, int command, Integer time, Integer save, Integer samplingRate); + + void uploadMediaDataForSingle(String phoneNumber, Long mediaId, Integer delete); + + JTMediaAttribute queryMediaAttribute(String phoneNumber); + + void changeStreamType(String phoneNumber, Integer channelId, Integer streamType); + + void recordDownload(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType, OutputStream outputStream, CommonCallback> fileCallback); + + PageInfo getChannelList(int page, int count, int deviceId, String query); + + void updateChannel(JTChannel channel); + + void addChannel(JTChannel channel); + + void deleteChannelById(Integer id); + + JTDevice getDeviceById(Integer deviceId); + + void updateDevicePosition(String phoneNumber, Double longitude, Double latitude); + + JTChannel getChannelByDbId(Integer id); + + String getRecordTempUrl(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType); + + void recordDownload(String filePath, ServletOutputStream outputStream); + + byte[] snap(String phoneNumber, int channelId); + + void uploadOneMedia(String phoneNumber, Long mediaId, ServletOutputStream outputStream, boolean delete); + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePTZServiceForJTImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePTZServiceForJTImpl.java new file mode 100644 index 000000000..f88a73d38 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePTZServiceForJTImpl.java @@ -0,0 +1,159 @@ +package com.genersoft.iot.vmp.jt1078.service.impl; + +import com.genersoft.iot.vmp.common.enums.ChannelDataType; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.service.IPTZService; +import com.genersoft.iot.vmp.gb28181.service.ISourcePTZService; +import com.genersoft.iot.vmp.jt1078.bean.JTChannel; +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.proc.response.*; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.units.qual.A; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.List; + +@Slf4j +@Service(ChannelDataType.PTZ_SERVICE + ChannelDataType.JT_1078) +public class SourcePTZServiceForJTImpl implements ISourcePTZService { + + @Autowired + private Ijt1078Service service; + + @Autowired + private JT1078Template jt1078Template; + + @Override + public void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); + if (jtChannel == null) { + callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); + return; + } + JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); + if (jtDevice == null) { + callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); + return; + } + + if (frontEndControlCode.getPan() == null && frontEndControlCode.getTilt() == null && frontEndControlCode.getZoom() == null) { + J9301 j9301 = new J9301(); + j9301.setChannel(jtChannel.getChannelId()); + j9301.setDirection(0); + j9301.setSpeed(0); + jt1078Template.ptzRotate(jtDevice.getPhoneNumber(), j9301, 6); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); + return; + } + + if (frontEndControlCode.getPan() != null || frontEndControlCode.getTilt() != null) { + J9301 j9301 = new J9301(); + j9301.setChannel(jtChannel.getChannelId()); + if (frontEndControlCode.getTilt() != null) { + if (frontEndControlCode.getTilt() == 0) { + j9301.setDirection(1); + }else if (frontEndControlCode.getTilt() == 1) { + j9301.setDirection(2); + } + j9301.setSpeed((int)(frontEndControlCode.getTilt()/100D * 255)); + } + + if (frontEndControlCode.getPan() != null) { + if (frontEndControlCode.getPan() == 0) { + j9301.setDirection(3); + }else if (frontEndControlCode.getPan() == 1) { + j9301.setDirection(4); + } + j9301.setSpeed((int)(frontEndControlCode.getPanSpeed()/100D * 255)); + } + jt1078Template.ptzRotate(jtDevice.getPhoneNumber(), j9301, 6); + } + if (frontEndControlCode.getZoom() != null) { + J9306 j9306 = new J9306(); + j9306.setChannel(jtChannel.getChannelId()); + j9306.setZoom(1 - frontEndControlCode.getZoom()); + jt1078Template.ptzZoom(jtDevice.getPhoneNumber(), j9306, 6); + } + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null); + } + + @Override + public void preset(CommonGBChannel channel, FrontEndControlCodeForPreset frontEndControlCode, ErrorCallback callback) { + callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); + } + + @Override + public void fi(CommonGBChannel channel, FrontEndControlCodeForFI frontEndControlCode, ErrorCallback callback) { + JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); + if (jtChannel == null) { + callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); + return; + } + JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); + if (jtDevice == null) { + callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); + return; + } + if (frontEndControlCode.getIris() != null) { + J9303 j9303 = new J9303(); + j9303.setChannel(jtChannel.getChannelId()); + j9303.setIris(1 - frontEndControlCode.getIris()); + jt1078Template.ptzIris(jtDevice.getPhoneNumber(), j9303, 6); + } + if (frontEndControlCode.getFocus() != null) { + J9302 j9302 = new J9302(); + j9302.setChannel(jtChannel.getChannelId()); + j9302.setFocalDirection(1 - frontEndControlCode.getFocus()); + jt1078Template.ptzFocal(jtDevice.getPhoneNumber(), j9302, 6); + } + } + + @Override + public void tour(CommonGBChannel channel, FrontEndControlCodeForTour frontEndControlCode, ErrorCallback callback) { + callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); + } + + @Override + public void scan(CommonGBChannel channel, FrontEndControlCodeForScan frontEndControlCode, ErrorCallback callback) { + callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); + } + + @Override + public void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback callback) { + callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); + } + + @Override + public void wiper(CommonGBChannel channel, FrontEndControlCodeForWiper frontEndControlCode, ErrorCallback callback) { + JTChannel jtChannel = service.getChannelByDbId(channel.getDataDeviceId()); + if (jtChannel == null) { + callback.run(ErrorCode.ERROR404.getCode(), "通道不存在", null); + return; + } + JTDevice jtDevice = service.getDeviceById(jtChannel.getTerminalDbId()); + if (jtDevice == null) { + callback.run(ErrorCode.ERROR404.getCode(), "设备不存在", null); + return; + } + J9304 j9304 = new J9304(); + j9304.setChannel(jtChannel.getChannelId()); + if (frontEndControlCode.getCode() == 1) { + j9304.setOn(1); + }else if (frontEndControlCode.getCode() == 0){ + j9304.setOn(0); + } + jt1078Template.ptzWiper(jtDevice.getPhoneNumber(), j9304, 6); + } + + @Override + public void queryPreset(CommonGBChannel channel, ErrorCallback> callback) { + callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java new file mode 100644 index 000000000..9c4ad082a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java @@ -0,0 +1,47 @@ +package com.genersoft.iot.vmp.jt1078.service.impl; + +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.enums.ChannelDataType; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.bean.Platform; +import com.genersoft.iot.vmp.gb28181.bean.PlayException; +import com.genersoft.iot.vmp.gb28181.service.IPlayService; +import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.sip.message.Response; + +@Slf4j +@Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.JT_1078) +public class SourcePlayServiceForJTImpl implements ISourcePlayService { + + @Autowired + private Ijt1078PlayService playService; + + @Override + public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { + // 部标设备通道 + try { + playService.start(channel.getDataDeviceId(), record, callback); + }catch (Exception e) { + log.info("[通用通道] 部标设备点播异常 {}", e.getMessage()); + callback.run(Response.BUSY_HERE, "busy here", null); + } + } + + @Override + public void stopPlay(CommonGBChannel channel, String stream) { + // 推流 + try { + playService.stop(channel.getDataDeviceId()); + }catch (Exception e) { + log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlaybackServiceForJTImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlaybackServiceForJTImpl.java new file mode 100644 index 000000000..13881ce2c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlaybackServiceForJTImpl.java @@ -0,0 +1,131 @@ +package com.genersoft.iot.vmp.jt1078.service.impl; + +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.enums.ChannelDataType; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo; +import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService; +import com.genersoft.iot.vmp.jt1078.bean.JTChannel; +import com.genersoft.iot.vmp.jt1078.bean.JTDevice; +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.dao.JTChannelMapper; +import com.genersoft.iot.vmp.jt1078.dao.JTTerminalMapper; +import com.genersoft.iot.vmp.jt1078.proc.request.J1205; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.units.qual.A; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service(ChannelDataType.PLAYBACK_SERVICE + ChannelDataType.JT_1078) +public class SourcePlaybackServiceForJTImpl implements ISourcePlaybackService { + + @Autowired + private JTTerminalMapper jtDeviceMapper; + + @Autowired + private JTChannelMapper jtChannelMapper; + + @Autowired + private JT1078Template jt1078Template; + + @Autowired + private Ijt1078PlayService playService; + + + @Override + public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback callback) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + + playService.playback(device.getPhoneNumber(), jtChannel.getChannelId(), DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime), + DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime), 0, 0, 0, 0, result -> { + callback.run(result.getCode(), result.getMsg(), result.getData()); + }); + } + + @Override + public void stopPlayback(CommonGBChannel channel, String stream) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + + playService.stopPlayback(device.getPhoneNumber(), jtChannel.getChannelId()); + } + + @Override + public void playbackPause(CommonGBChannel channel, String stream) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + + playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 1, 0, null); + } + + @Override + public void playbackResume(CommonGBChannel channel, String stream) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + + playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 0, 0, null); + } + + @Override + public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) { + // 因为seek是增量,比如15s处, 1078是具体的时间点,比如2025-10-10 23:21:12 这个一个绝对时间点。无法转换,故此处不做支持 + log.warn("[JT-通用通道] 回放seek, 尚不支持"); + } + + @Override + public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + + playService.playbackControl(device.getPhoneNumber(), jtChannel.getChannelId(), 0, (int)Math.floor(speed), null); + } + + @Override + public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(channel.getDataDeviceId()); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jtDeviceMapper.getDeviceById(jtChannel.getDataDeviceId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + List recordList = playService.getRecordList(device.getPhoneNumber(), jtChannel.getChannelId(), startTime, endTime); + if (recordList.isEmpty()) { + callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); + return; + } + + List recordInfoList = new ArrayList<>(); + for (J1205.JRecordItem jRecordItem : recordList) { + CommonRecordInfo commonRecordInfo = new CommonRecordInfo(); + commonRecordInfo.setStartTime(jRecordItem.getStartTime()); + commonRecordInfo.setEndTime(jRecordItem.getEndTime()); + commonRecordInfo.setFileSize(jRecordItem.getSize() + ""); + recordInfoList.add(commonRecordInfo); + } + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), recordInfoList); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078PlayServiceImpl.java new file mode 100644 index 000000000..f9096780b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078PlayServiceImpl.java @@ -0,0 +1,712 @@ +package com.genersoft.iot.vmp.jt1078.service.impl; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.VideoManagerConstants; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.conf.ftpServer.FtpSetting; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent; +import com.genersoft.iot.vmp.jt1078.proc.request.J1205; +import com.genersoft.iot.vmp.jt1078.proc.response.*; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.media.bean.MediaInfo; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.event.hook.Hook; +import com.genersoft.iot.vmp.media.event.hook.HookData; +import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; +import com.genersoft.iot.vmp.media.event.hook.HookType; +import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; +import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; +import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent; +import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent; +import com.genersoft.iot.vmp.media.service.IMediaServerService; +import com.genersoft.iot.vmp.service.ISendRtpServerService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; +import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.utils.MediaServerUtils; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.sip.message.Response; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +@Slf4j +public class jt1078PlayServiceImpl implements Ijt1078PlayService { + + public static final String talkApp = "jt_talk"; + + @Autowired + private ISendRtpServerService sendRtpServerService; + + @Autowired + private Ijt1078Service jt1078Service; + + @Autowired + private JT1078Template jt1078Template; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private HookSubscribe subscribe; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private DynamicTask dynamicTask; + + @Autowired + private UserSetting userSetting; + + /** + * 流到来的处理 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaArrivalEvent event) { + if (event.getApp().equals(talkApp) && event.getStream().endsWith("_talk")) { + // 收到对JT讲的流 + if (event.getStream().indexOf("_") <= 0) { + log.info("[JT-对讲流到来] 流格式有误,stream应该为jt_[phoneNumber]_[channelId]_talk"); + return; + } + String[] streamArray = event.getStream().split("_"); + if (streamArray.length != 4) { + log.info("[JT-对讲流到来] 流格式有误,stream应该为jt_[phoneNumber]_[channelId]_talk"); + return; + } + String phoneNumber = streamArray[1]; + String channelId = streamArray[2]; + JTDevice device = jt1078Service.getDevice(phoneNumber); + if (device == null) { + log.info("[JT-对讲流到来] 未找到设备{}", phoneNumber); + return; + } + sendTalk(device, Integer.valueOf(channelId), event.getMediaServer(), event.getApp(), event.getStream()); + + } + } + + /** + * 流离开的处理 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaDepartureEvent event) { + + } + + /** + * 流未找到的处理 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaNotFoundEvent event) { + if (!userSetting.getAutoApplyPlay()) { + return; + } + JTMediaStreamType jtMediaStreamType = checkStreamFromJt(event.getStream()); + if (jtMediaStreamType == null){ + return; + } + String[] streamParamArray = event.getStream().split("_"); + String phoneNumber = streamParamArray[1]; + int channelId = Integer.parseInt(streamParamArray[2]); + String params = event.getParams(); + Map paramMap = MediaServerUtils.urlParamToMap(params); + int type = 0; + try { + type = Integer.parseInt(paramMap.get("type")); + }catch (NumberFormatException ignored) {} + if (jtMediaStreamType.equals(JTMediaStreamType.PLAY)) { + play(phoneNumber, channelId, 0, null); + }else if (jtMediaStreamType.equals(JTMediaStreamType.PLAYBACK)) { + String startTimeParam = DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(streamParamArray[3]); + String endTimeParam = DateUtil.jt1078Toyyyy_MM_dd_HH_mm_ss(streamParamArray[4]); + int rate = 0; + int playbackType = 0; + int playbackSpeed = 0; + try { + rate = Integer.parseInt(paramMap.get("rate")); + playbackType = Integer.parseInt(paramMap.get("playbackType")); + playbackSpeed = Integer.parseInt(paramMap.get("playbackSpeed")); + }catch (NumberFormatException ignored) {} + playback(phoneNumber, channelId, startTimeParam, endTimeParam, type, rate, playbackType, playbackSpeed, null); + } + } + + + /** + * 校验流是否是属于部标的 + */ + @Override + public JTMediaStreamType checkStreamFromJt(String stream) { + if (!stream.startsWith("jt_")) { + return null; + } + String[] streamParamArray = stream.split("_"); + if (streamParamArray.length == 3) { + return JTMediaStreamType.PLAY; + }else if (streamParamArray.length == 5) { + return JTMediaStreamType.PLAYBACK; + }else if (streamParamArray.length == 4) { + return JTMediaStreamType.TALK; + }else { + return null; + } + } + + private final Map>>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); + + @Override + public void play(String phoneNumber, Integer channelId, int type, CommonCallback> callback) { + JTDevice device = jt1078Service.getDevice(phoneNumber); + if (device == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); + } + jt1078Template.checkTerminalStatus(phoneNumber); + JTChannel channel = jt1078Service.getChannel(device.getId(), channelId); + if (channel == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道不存在"); + } + play(device, channel, type, callback); + } + + private void play(JTDevice device, JTChannel channel, int type, CommonCallback> callback) { + String phoneNumber = device.getPhoneNumber(); + int channelId = channel.getChannelId(); + String app = "1078"; + String stream = phoneNumber + "_" + channelId; + // 检查流是否已经存在,存在则返回 + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; + List>> errorCallbacks = inviteErrorCallbackMap.computeIfAbsent(playKey, k -> new ArrayList<>()); + errorCallbacks.add(callback); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + if (streamInfo != null) { + MediaServer mediaServer = streamInfo.getMediaServer(); + if (mediaServer != null) { + // 查询流是否存在,不存在则删除缓存数据 + MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, app, streamInfo.getStream()); + if (mediaInfo != null) { + log.info("[JT-点播] 点播已经存在,直接返回, phoneNumber: {}, channelId: {}", phoneNumber, channelId); + for (CommonCallback> errorCallback : errorCallbacks) { + errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo)); + } + return; + } + } + // 清理数据 + redisTemplate.delete(playKey); + } + + MediaServer mediaServer; + if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { + mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); + } else { + mediaServer = mediaServerService.getOne(device.getMediaServerId()); + } + if (mediaServer == null) { + for (CommonCallback> errorCallback : errorCallbacks) { + errorCallback.run(new WVPResult<>(InviteErrorCode.FAIL.getCode(), "未找到可用的媒体节点", streamInfo)); + } + return; + } + // 设置hook监听 + Hook hook = Hook.getInstance(HookType.on_media_arrival, app, stream, mediaServer.getId()); + subscribe.addSubscribe(hook, (hookData) -> { + dynamicTask.stop(playKey); + log.info("[JT-点播] 点播成功, 手机号: {}, 通道: {}", phoneNumber, channelId); + // TODO 发送9105 实时音视频传输状态通知, 通知丢包率 + StreamInfo info = onPublishHandler(mediaServer, hookData, phoneNumber, channelId); + + for (CommonCallback> errorCallback : errorCallbacks) { + if (errorCallback == null) { + continue; + } + errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), info)); + } + subscribe.removeSubscribe(hook); + redisTemplate.opsForValue().set(playKey, info); + // 截图 + String path = "snap"; + String fileName = phoneNumber + "_" + channelId + ".jpg"; + // 请求截图 + log.info("[请求截图]: " + fileName); + mediaServerService.getSnap(mediaServer, app, stream, 15, 1, path, fileName); + }); + // 开启收流端口 + SSRCInfo ssrcInfo = mediaServerService.openJTTServer(mediaServer, stream, null, false, !channel.isHasAudio(), 1); + if (ssrcInfo == null) { + stopPlay(phoneNumber, channelId); + return; + } + + // 设置超时监听 + dynamicTask.startDelay(playKey, () -> { + log.info("[JT-点播] 超时, phoneNumber: {}, channelId: {}", phoneNumber, channelId); + for (CommonCallback> errorCallback : errorCallbacks) { + errorCallback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), + InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null)); + } + mediaServerService.closeJTTServer(mediaServer, stream, null); + subscribe.removeSubscribe(hook); + stopPlay(phoneNumber, channelId); + }, userSetting.getPlayTimeout()); + + log.info("[JT-点播] phoneNumber: {}, channelId: {},IP: {}, 端口: {}", phoneNumber, channelId, mediaServer.getSdpIp(), ssrcInfo.getPort()); + J9101 j9101 = new J9101(); + j9101.setChannel(channelId); + j9101.setIp(mediaServer.getSdpIp()); + j9101.setRate(1); + j9101.setTcpPort(ssrcInfo.getPort()); + j9101.setUdpPort(ssrcInfo.getPort()); + j9101.setType(type); + jt1078Template.startLive(phoneNumber, j9101, 6); + } + + public StreamInfo onPublishHandler(MediaServer mediaServerItem, HookData hookData, String phoneNumber, Integer channelId) { + StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, "1078", hookData.getStream(), hookData.getMediaInfo(), null); + streamInfo.setDeviceId(phoneNumber); + streamInfo.setChannelId(channelId); + return streamInfo; + } + + @Override + public void stopPlay(String phoneNumber, Integer channelId) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + // 清理回调 + List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); + if (generalCallbacks != null && !generalCallbacks.isEmpty()) { + for (CommonCallback> callback : generalCallbacks) { + callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); + } + } + jt1078Template.checkTerminalStatus(phoneNumber); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + // 发送停止命令 + J9102 j9102 = new J9102(); + j9102.setChannel(channelId); + j9102.setCommand(0); + j9102.setCloseType(0); + j9102.setStreamType(1); + jt1078Template.stopLive(phoneNumber, j9102, 6); + log.info("[JT-停止点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); + // 删除缓存数据 + if (streamInfo != null) { + // 关闭rtpServer + mediaServerService.closeJTTServer(streamInfo.getMediaServer(), streamInfo.getStream(), null); + redisTemplate.delete(playKey); + } + + } + + @Override + public void pausePlay(String phoneNumber, Integer channelId) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + if (streamInfo == null) { + log.info("[JT-暂停点播] 未找到点播信息 phoneNumber: {}, channelId: {}", phoneNumber, channelId); + } + log.info("[JT-暂停点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); + // 发送暂停命令 + J9102 j9102 = new J9102(); + j9102.setChannel(channelId); + j9102.setCommand(2); + j9102.setCloseType(0); + j9102.setStreamType(1); + jt1078Template.stopLive(phoneNumber, j9102, 6); + } + + @Override + public void continueLivePlay(String phoneNumber, Integer channelId) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + if (streamInfo == null) { + log.info("[JT-继续点播] 未找到点播信息 phoneNumber: {}, channelId: {}", phoneNumber, channelId); + } + log.info("[JT-继续点播] phoneNumber: {}, channelId: {}", phoneNumber, channelId); + // 发送暂停命令 + J9102 j9102 = new J9102(); + j9102.setChannel(channelId); + j9102.setCommand(2); + j9102.setCloseType(0); + j9102.setStreamType(1); + jt1078Template.stopLive(phoneNumber, j9102, 6); + } + + @Override + public List getRecordList(String phoneNumber, Integer channelId, String startTime, String endTime) { + log.info("[JT-查询录像列表] phoneNumber: {}, channelId: {}, startTime: {}, endTime: {}" + , phoneNumber, channelId, startTime, endTime); + // 发送请求录像列表命令 + J9205 j9205 = new J9205(); + j9205.setChannelId(channelId); + j9205.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); + j9205.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); + j9205.setMediaType(0); + j9205.setStreamType(0); + j9205.setStorageType(0); + List JRecordItemList = (List) jt1078Template.queryBackTime(phoneNumber, j9205, 20); + if (JRecordItemList == null || JRecordItemList.isEmpty()) { + return null; + } + log.info("[JT-查询录像列表] phoneNumber: {}, channelId: {}, startTime: {}, endTime: {}, 结果: {}条" + , phoneNumber, channelId, startTime, endTime, JRecordItemList.size()); + return JRecordItemList; + } + + + + @Override + public void playback(String phoneNumber, Integer channelId, String startTime, String endTime, Integer type, + Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback) { + JTDevice device = jt1078Service.getDevice(phoneNumber); + if (device == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); + } + jt1078Template.checkTerminalStatus(phoneNumber); + JTChannel channel = jt1078Service.getChannel(device.getId(), channelId); + if (channel == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道不存在"); + } + playback(device, channel, startTime, endTime, type, rate, playbackType, playbackSpeed, callback); + + } + + /** + * 回放 + * @param device 设备 + * @param channel 通道 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param type 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 + * @param rate 码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0) + * @param playbackType 回放方式:0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传 + * @param playbackSpeed 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0) + * @param callback 结束回调 + */ + private void playback(JTDevice device, JTChannel channel, String startTime, String endTime, Integer type, + Integer rate, Integer playbackType, Integer playbackSpeed, CommonCallback> callback) { + + String phoneNumber = device.getPhoneNumber(); + Integer channelId = channel.getChannelId(); + log.info("[JT-回放] 回放,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {}, 音视频类型: {}, 码流类型: {}, " + + "回放方式: {}, 快进或快退倍数: {}", phoneNumber, channelId, startTime, endTime, type, rate, playbackType, playbackSpeed); + // 检查流是否已经存在,存在则返回 + String playbackKey = VideoManagerConstants.INVITE_INFO_1078_PLAYBACK + phoneNumber + ":" + channelId; + List>> errorCallbacks = inviteErrorCallbackMap.computeIfAbsent(playbackKey, k -> new ArrayList<>()); + errorCallbacks.add(callback); + String logInfo = String.format("phoneNumber:%s, channelId:%s, startTime:%s, endTime:%s", phoneNumber, channelId, startTime, endTime); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playbackKey); + if (streamInfo != null) { + + mediaServerService.closeJTTServer(streamInfo.getMediaServer(), streamInfo.getStream(), null); + // 清理数据 + redisTemplate.delete(playbackKey); + } + + String app = "1078"; + String stream = String.format("%s_%s_%s_%s", phoneNumber, channelId, + DateUtil.yyyy_MM_dd_HH_mm_ssToUrl(startTime), DateUtil.yyyy_MM_dd_HH_mm_ssToUrl(endTime)); + MediaServer mediaServer; + if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { + mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); + } else { + mediaServer = mediaServerService.getOne(device.getMediaServerId()); + } + if (mediaServer == null) { + for (CommonCallback> errorCallback : errorCallbacks) { + errorCallback.run(new WVPResult<>(InviteErrorCode.FAIL.getCode(), "未找到可用的媒体节点", streamInfo)); + } + return; + } + // 设置hook监听 + Hook hookSubscribe = Hook.getInstance(HookType.on_media_arrival, app, stream, mediaServer.getId()); + subscribe.addSubscribe(hookSubscribe, (hookData) -> { + dynamicTask.stop(playbackKey); + log.info("[JT-回放] 回放成功, logInfo: {}", logInfo); + StreamInfo info = onPublishHandler(mediaServer, hookData, phoneNumber, channelId); + + for (CommonCallback> errorCallback : errorCallbacks) { + if (errorCallback == null) { + continue; + } + errorCallback.run(new WVPResult<>(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), info)); + } + subscribe.removeSubscribe(hookSubscribe); + redisTemplate.opsForValue().set(playbackKey, info); + }); + // 设置超时监听 + dynamicTask.startDelay(playbackKey, () -> { + log.info("[JT-回放] 回放超时, logInfo: {}", logInfo); + for (CommonCallback> errorCallback : errorCallbacks) { + errorCallback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode(), + InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getMsg(), null)); + } + mediaServerService.closeJTTServer(mediaServer, stream, null); + subscribe.removeSubscribe(hookSubscribe); + }, userSetting.getPlayTimeout()); + + // 开启收流端口 + SSRCInfo ssrcInfo = mediaServerService.openJTTServer(mediaServer, stream, null, false, !channel.isHasAudio(), 1); + log.info("[JT-回放] logInfo: {}, 端口: {}", logInfo, ssrcInfo.getPort()); + J9201 j9201 = new J9201(); + j9201.setChannel(channelId); + j9201.setIp(mediaServer.getSdpIp()); + if (rate != null) { + j9201.setRate(rate); + } + if (playbackType != null) { + j9201.setPlaybackType(playbackType); + } + if (playbackSpeed != null) { + j9201.setPlaybackSpeed(playbackSpeed); + } + + j9201.setTcpPort(ssrcInfo.getPort()); + j9201.setUdpPort(ssrcInfo.getPort()); + j9201.setType(type); + j9201.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); + j9201.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); + jt1078Template.startBackLive(phoneNumber, j9201, 20); + + } + + @Override + public void playbackControl(String phoneNumber, Integer channelId, Integer command, Integer playbackSpeed, String time) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAYBACK + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + if (command == 2) { + log.info("[JT-停止回放] phoneNumber: {}, channelId: {}, command: {}, playbackSpeed: {}, time: {}", + phoneNumber, channelId, command, playbackSpeed, time); + // 结束回放 + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + // 删除缓存数据 + if (streamInfo != null) { + // 关闭rtpServer + mediaServerService.closeJTTServer(streamInfo.getMediaServer(), streamInfo.getStream(), null); + } + // 清理回调 + List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); + if (generalCallbacks != null && !generalCallbacks.isEmpty()) { + for (CommonCallback> callback : generalCallbacks) { + if (callback == null) { + continue; + } + callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); + } + } + }else { + log.info("[JT-回放控制] phoneNumber: {}, channelId: {}, command: {}, playbackSpeed: {}, time: {}", + phoneNumber, channelId, command, playbackSpeed, time); + } + // 发送停止命令 + J9202 j9202 = new J9202(); + j9202.setChannel(channelId); + j9202.setPlaybackType(command); + + if (playbackSpeed != null) { + j9202.setPlaybackSpeed(playbackSpeed); + + } + if (!ObjectUtils.isEmpty(time)) { + j9202.setPlaybackTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(time)); + } + jt1078Template.controlBackLive(phoneNumber, j9202, 4); + } + + @Override + public void stopPlayback(String phoneNumber, Integer channelId) { + playbackControl(phoneNumber, channelId, 2, null, null); + } + + /** + * 监听发流停止 + */ + @EventListener + public void onApplicationEvent(MediaSendRtpStoppedEvent event) { + + List sendRtpInfos = sendRtpServerService.queryByStream(event.getStream()); + if (sendRtpInfos.isEmpty()) { + return; + } + for (SendRtpInfo sendRtpInfo : sendRtpInfos) { + if (!sendRtpInfo.isOnlyAudio() || ObjectUtils.isEmpty(sendRtpInfo.getChannelId())) { + continue; + } + if (!sendRtpInfo.getSsrc().contains("_")) { + continue; + } + sendRtpServerService.delete(sendRtpInfo); + String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + sendRtpInfo.getApp() + ":" + sendRtpInfo.getStream(); + redisTemplate.delete(playKey); + } + } + + + @Override + public StreamInfo startTalk(String phoneNumber, Integer channelId) { + // 检查流是否已经存在,存在则返回 + String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + phoneNumber + ":" + channelId; + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + + if (streamInfo != null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "对讲进行中"); + } + + JTDevice device = jt1078Service.getDevice(phoneNumber); + Assert.notNull(device, "部标设备不存在"); + + String stream = "jt_" + phoneNumber + "_" + channelId + "_talk"; + + MediaServer mediaServer; + if (org.springframework.util.ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { + mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); + } else { + mediaServer = mediaServerService.getOne(device.getMediaServerId()); + } + + // 检查待发送的流是否存在, + MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, talkApp, stream); + Assert.isNull(mediaInfo, "对讲已经存在"); + return mediaServerService.getStreamInfoByAppAndStream(mediaServer, talkApp, stream, null, null, null, false); + + } + private void sendTalk(JTDevice device, Integer channelId, MediaServer mediaServer, String app, String stream) { + // 检查待发送的流是否存在, + MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, app, stream); + if (mediaInfo == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), app + "/" + stream + "流不存在"); + } + + String phoneNumber = device.getPhoneNumber(); + + // 开启收流端口, zlm发送1078的rtp流需要将ssrc字段设置为 imei_channel格式 + String ssrc = device.getPhoneNumber() + "_" + channelId; + SendRtpInfo sendRtpInfo = sendRtpServerService.createSendRtpInfo(mediaServer, null, null, ssrc, phoneNumber, talkApp, stream, channelId, true, false); + sendRtpInfo.setTcpActive(true); + sendRtpInfo.setUsePs(false); + sendRtpInfo.setOnlyAudio(true); + sendRtpInfo.setReceiveStream(stream + "_talk"); + + // 设置hook监听 + Hook hook = Hook.getInstance(HookType.on_media_arrival, "1078", sendRtpInfo.getReceiveStream(), mediaServer.getId()); + subscribe.addSubscribe(hook, (hookData) -> { + log.info("[JT-对讲] 对讲连接建立, phoneNumber: {}, channelId: {}", phoneNumber, channelId); + subscribe.removeSubscribe(hook); + // 存储发流信息 + sendRtpServerService.update(sendRtpInfo); + }); + Hook hookForDeparture = Hook.getInstance(HookType.on_media_departure, app, stream, mediaServer.getId()); + subscribe.addSubscribe(hookForDeparture, (hookData) -> { + log.info("[JT-对讲] 对讲时源流注销, app: {}. stream: {}, phoneNumber: {}, channelId: {}", app, stream, phoneNumber, channelId); + stopTalk(phoneNumber, channelId); + }); + + Integer localPort = mediaServerService.startSendRtpPassive(mediaServer, sendRtpInfo, userSetting.getPlayTimeout()); + + log.info("[JT-对讲] phoneNumber: {}, channelId: {}, 收发端口: {}, app: {}, stream: {}", + phoneNumber, channelId, localPort, app, stream); + J9101 j9101 = new J9101(); + j9101.setChannel(channelId); + j9101.setIp(mediaServer.getSdpIp()); + j9101.setRate(1); + j9101.setTcpPort(sendRtpInfo.getLocalPort()); + j9101.setUdpPort(sendRtpInfo.getLocalPort()); + j9101.setType(4); + jt1078Template.startLive(phoneNumber, j9101, 6); + + log.info("[JT-对讲] 对讲消息下发成功, phoneNumber: {}, channelId: {}", phoneNumber, channelId); + // 存储发流信息 +// sendRtpServerService.update(sendRtpInfo); + } + + @Override + public void stopTalk(String phoneNumber, Integer channelId) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_TALK + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + // 发送停止命令 + J9102 j9102 = new J9102(); + j9102.setChannel(channelId); + j9102.setCommand(4); + j9102.setCloseType(0); + j9102.setStreamType(1); + jt1078Template.stopLive(phoneNumber, j9102, 6); + log.info("[JT-停止对讲] phoneNumber: {}, channelId: {}", phoneNumber, channelId); + // 删除缓存数据 + if (streamInfo != null) { + redisTemplate.delete(playKey); + // 关闭rtpServer + mediaServerService.closeJTTServer(streamInfo.getMediaServer(), streamInfo.getStream(), null); + } + // 清理回调 + List>> generalCallbacks = inviteErrorCallbackMap.get(playKey); + if (generalCallbacks != null && !generalCallbacks.isEmpty()) { + for (CommonCallback> callback : generalCallbacks) { + callback.run(new WVPResult<>(InviteErrorCode.ERROR_FOR_FINISH.getCode(), InviteErrorCode.ERROR_FOR_FINISH.getMsg(), null)); + } + } + } + + @Override + public void start(Integer channelId, Boolean record, ErrorCallback callback) { + JTChannel channel = jt1078Service.getChannelByDbId(channelId); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + play(device, channel, 0, + result -> callback.run(result.getCode(), result.getMsg(), result.getData())); + } + + @Override + public void stop(Integer channelId) { + JTChannel channel = jt1078Service.getChannelByDbId(channelId); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); + Assert.notNull(device, "设备不存在"); + stopPlay(device.getPhoneNumber(), channel.getChannelId()); + } + + @Override + public void playBack(Integer channelId, Long startTime, Long stopTime, ErrorCallback callback) { + if (startTime == null || stopTime == null) { + throw new PlayException(Response.BAD_REQUEST, "bad request"); + } + JTChannel channel = jt1078Service.getChannelByDbId(channelId); + Assert.notNull(channel, "通道不存在"); + JTDevice device = jt1078Service.getDeviceById(channel.getTerminalDbId()); + Assert.notNull(device, "设备不存在"); + jt1078Template.checkTerminalStatus(device.getPhoneNumber()); + String startTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(startTime); + String stopTimeStr = DateUtil.timestampTo_yyyy_MM_dd_HH_mm_ss(stopTime); + playback(device, channel, startTimeStr, stopTimeStr, 0, 1, 0, 0, + result -> callback.run(result.getCode(), result.getMsg(), result.getData())); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078ServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078ServiceImpl.java new file mode 100644 index 000000000..1e3df6711 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/jt1078ServiceImpl.java @@ -0,0 +1,920 @@ +package com.genersoft.iot.vmp.jt1078.service.impl; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.VideoManagerConstants; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.ftpServer.FtpFileSystemFactory; +import com.genersoft.iot.vmp.conf.ftpServer.FtpSetting; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.conf.ftpServer.UserManager; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; +import com.genersoft.iot.vmp.jt1078.bean.*; +import com.genersoft.iot.vmp.jt1078.bean.common.ConfigAttribute; +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.dao.JTChannelMapper; +import com.genersoft.iot.vmp.jt1078.dao.JTTerminalMapper; +import com.genersoft.iot.vmp.jt1078.event.DeviceUpdateEvent; +import com.genersoft.iot.vmp.jt1078.event.JTPositionEvent; +import com.genersoft.iot.vmp.jt1078.proc.response.*; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; +import com.genersoft.iot.vmp.jt1078.session.FtpDownloadManager; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; +import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.*; + +@Service +@Slf4j +public class jt1078ServiceImpl implements Ijt1078Service { + + @Autowired + private JTTerminalMapper jtDeviceMapper; + + @Autowired + private JTChannelMapper jtChannelMapper; + + @Autowired + private JT1078Template jt1078Template; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private IGbChannelService channelService; + + @Autowired + private DynamicTask dynamicTask; + + @Autowired + private UserSetting userSetting; + + @Autowired + private FtpSetting ftpSetting; + + @Autowired + private UserManager ftpUserManager; + + @Autowired + private FtpFileSystemFactory fileSystemFactory; + + @Autowired + private FtpDownloadManager downloadManager; + + // 服务启动后五分钟内没有尽量连接的设备设置为离线 + @PostConstruct + public void init(){ + // 检查session与在线终端是是否对应 不对应则设置终端离线 + List deviceList = jtDeviceMapper.getDeviceList(null, true); + if (deviceList.isEmpty()) { + return; + } + for (JTDevice device : deviceList) { + Session session = SessionManager.INSTANCE.get(device.getPhoneNumber()); + if (session == null) { + device.setStatus(false); + // 通道发送状态变化通知 + List jtChannels = jtChannelMapper.selectAll(device.getId(), null); + List channelList = new ArrayList<>(); + for (JTChannel jtChannel : jtChannels) { + if (jtChannel.getGbId() > 0) { + jtChannel.setGbStatus("OFF"); + channelList.add(jtChannel); + } + } + channelService.updateStatus(channelList); + updateDevice(device); + } + } + } + + /** + * 流到来的处理 + */ + @Async("taskExecutor") + @org.springframework.context.event.EventListener + public void onApplicationEvent(MediaArrivalEvent event) { + + } + + /** + * 流离开的处理 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaDepartureEvent event) { + + } + + /** + * 设备更新的通知 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(DeviceUpdateEvent event) { + JTDevice device = event.getDevice(); + if (device == null || device.getPhoneNumber() == null) { + return; + } + JTDevice deviceInDb = getDevice(event.getDevice().getPhoneNumber()); + if (deviceInDb.isStatus() != device.isStatus()) { + // 通道发送状态变化通知 + List jtChannels = jtChannelMapper.selectAll(deviceInDb.getId(), null); + List channelList = new ArrayList<>(); + for (JTChannel jtChannel : jtChannels) { + if (jtChannel.getGbId() > 0) { + jtChannel.setGbStatus("OFF"); + channelList.add(jtChannel); + } + } + channelService.updateStatus(channelList); + } + updateDevice(event.getDevice()); + } + + /** + * 位置更新的通知 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(JTPositionEvent event) { + if (event.getPhoneNumber() == null || event.getPositionInfo() == null + || event.getPositionInfo().getLongitude() == null || event.getPositionInfo().getLatitude() == null) { + return; + } + JTDevice device = getDevice(event.getPhoneNumber()); + if (device == null) { + return; + } + device.setLongitude(event.getPositionInfo().getLongitude()); + device.setLatitude(event.getPositionInfo().getLatitude()); + updateDevice(device); + + // 通道发送状态变化通知 + List jtChannels = jtChannelMapper.selectAll(device.getId(), null); + List channelList = new ArrayList<>(); + for (JTChannel jtChannel : jtChannels) { + if (jtChannel.getGbId() > 0) { + jtChannel.setGbLongitude(event.getPositionInfo().getLongitude()); + jtChannel.setGbLatitude(event.getPositionInfo().getLatitude()); + if (event.getPositionInfo().getAltitude() != null) { + jtChannel.setGpsAltitude((double) event.getPositionInfo().getAltitude()); + }else { + jtChannel.setGpsAltitude(0d); + } + if (event.getPositionInfo().getDirection() != null) { + jtChannel.setGpsDirection((double) event.getPositionInfo().getDirection()); + }else { + jtChannel.setGpsDirection(0d); + } + if (event.getPositionInfo().getTime() != null) { + jtChannel.setGpsTime(event.getPositionInfo().getTime()); + }else { + jtChannel.setGpsTime(DateUtil.getNow()); + } + channelList.add(jtChannel); + } + } + channelService.updateGPS(channelList); + } + + + /** + * 校验流是否是属于部标的 + */ + @Override + public JTMediaStreamType checkStreamFromJt(String stream) { + String[] streamParamArray = stream.split("_"); + if (streamParamArray.length == 2) { + return JTMediaStreamType.PLAY; + }else if (streamParamArray.length == 4) { + return JTMediaStreamType.PLAYBACK; + }else if (streamParamArray.length == 5) { + return JTMediaStreamType.TALK; + }else { + return null; + } + } + + @Override + public JTDevice getDevice(String phoneNumber) { + return jtDeviceMapper.getDevice(phoneNumber); + } + + @Override + public JTChannel getChannel(Integer terminalDbId, Integer channelId) { + return jtChannelMapper.selectChannelByChannelId(terminalDbId, channelId); + } + + @Override + public void updateDevice(JTDevice device) { + device.setUpdateTime(DateUtil.getNow()); + jtDeviceMapper.updateDevice(device); + } + + @Override + public PageInfo getDeviceList(int page, int count, String query, Boolean online) { + PageHelper.startPage(page, count); + if (query != null) { + query = query.replaceAll("/", "//") + .replaceAll("%", "/%") + .replaceAll("_", "/_"); + } + List all = jtDeviceMapper.getDeviceList(query, online); + return new PageInfo<>(all); + } + + @Override + public void addDevice(JTDevice device) { + JTDevice deviceInDb = jtDeviceMapper.getDevice(device.getPhoneNumber()); + if (deviceInDb != null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备" + device.getPhoneNumber() + "已存在"); + } + device.setCreateTime(DateUtil.getNow()); + device.setUpdateTime(DateUtil.getNow()); + jtDeviceMapper.addDevice(device); + } + + @Override + public void deleteDeviceByPhoneNumber(String phoneNumber) { + jtDeviceMapper.deleteDeviceByPhoneNumber(phoneNumber); + } + + @Override + public void updateDeviceStatus(boolean connected, String phoneNumber) { + jtDeviceMapper.updateDeviceStatus(connected, phoneNumber); + } + + + @Override + public void recordDownload(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, + Integer mediaType, Integer streamType, Integer storageType, OutputStream outputStream, CommonCallback> fileCallback) { + String filePath = UUID.randomUUID().toString(); + fileSystemFactory.addOutputStream(filePath, outputStream); + dynamicTask.startDelay(filePath, ()->{ + fileSystemFactory.removeOutputStream(filePath); + }, 2*60*60*1000); + Session session = SessionManager.INSTANCE.get(phoneNumber); + Assert.notNull(session, "连接不存在"); + InetSocketAddress socketAddress = session.getLoadAddress(); + String hostName = socketAddress.getHostName(); + + BaseUser randomUser = ftpUserManager.getRandomUser(); + + log.info("[JT-录像] 下载,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {} 上传IP: {} 等待上传文件路径: {} 用户名: {}, 密码: {} ", + phoneNumber, channelId, startTime, endTime, hostName, filePath, randomUser.getName(), randomUser.getPassword()); + // 发送停止命令 + J9206 j92026 = new J9206(); + j92026.setChannelId(channelId); + j92026.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); + j92026.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); + j92026.setServerIp(hostName); + j92026.setPort(ftpSetting.getPort()); + j92026.setUsername(randomUser.getName()); + j92026.setPassword(randomUser.getPassword()); + j92026.setPath(filePath); + + if (mediaType != null) { + j92026.setMediaType(mediaType); + } + if (streamType != null) { + j92026.setStreamType(streamType); + } + if (storageType != null) { + j92026.setStorageType(storageType); + } + if (alarmSign != null) { + j92026.setAlarmSign(alarmSign); + } + jt1078Template.fileUpload(phoneNumber, j92026, 7200); + } + + @Override + public void ptzControl(String phoneNumber, Integer channelId, String command, int speed) { + // 发送停止命令 + switch (command) { + case "left": + case "right": + case "up": + case "down": + case "stop": + J9301 j9301 = new J9301(); + j9301.setChannel(channelId); + switch (command) { + case "left": + j9301.setDirection(3); + j9301.setSpeed(speed); + break; + case "right": + j9301.setDirection(4); + j9301.setSpeed(speed); + break; + case "up": + j9301.setDirection(1); + j9301.setSpeed(speed); + break; + case "down": + j9301.setDirection(2); + j9301.setSpeed(speed); + break; + case "stop": + j9301.setDirection(0); + j9301.setSpeed(0); + break; + } + jt1078Template.ptzRotate(phoneNumber, j9301, 6); + break; + + case "zoomin": + case "zoomout": + J9306 j9306 = new J9306(); + j9306.setChannel(channelId); + if (command.equals("zoomin")) { + j9306.setZoom(0); + } else { + j9306.setZoom(1); + } + jt1078Template.ptzZoom(phoneNumber, j9306, 6); + break; + case "irisin": + case "irisout": + J9303 j9303 = new J9303(); + j9303.setChannel(channelId); + if (command.equals("irisin")) { + j9303.setIris(0); + } else { + j9303.setIris(1); + } + jt1078Template.ptzIris(phoneNumber, j9303, 6); + break; + case "focusnear": + case "focusfar": + J9302 j9302 = new J9302(); + j9302.setChannel(channelId); + if (command.equals("focusfar")) { + j9302.setFocalDirection(0); + } else { + j9302.setFocalDirection(1); + } + jt1078Template.ptzFocal(phoneNumber, j9302, 6); + break; + + } + } + + @Override + public void supplementaryLight(String phoneNumber, Integer channelId, String command) { + J9305 j9305 = new J9305(); + j9305.setChannel(channelId); + if (command.equalsIgnoreCase("on")) { + j9305.setOn(1); + } else { + j9305.setOn(0); + } + jt1078Template.ptzSupplementaryLight(phoneNumber, j9305, 6); + } + + @Override + public void wiper(String phoneNumber, Integer channelId, String command) { + J9304 j9304 = new J9304(); + j9304.setChannel(channelId); + if (command.equalsIgnoreCase("on")) { + j9304.setOn(1); + } else { + j9304.setOn(0); + } + jt1078Template.ptzWiper(phoneNumber, j9304, 6); + } + + @Override + public JTDeviceConfig queryConfig(String phoneNumber, String[] params) { + if (phoneNumber == null) { + return null; + } + if (params == null || params.length == 0) { + J8104 j8104 = new J8104(); + return (JTDeviceConfig) jt1078Template.getDeviceConfig(phoneNumber, j8104, 20); + } else { + long[] paramBytes = new long[params.length]; + for (int i = 0; i < params.length; i++) { + try { + Field field = JTDeviceConfig.class.getDeclaredField(params[i]); + if (field.isAnnotationPresent(ConfigAttribute.class)) { + ConfigAttribute configAttribute = field.getAnnotation(ConfigAttribute.class); + if (configAttribute == null) { + log.warn("[查询设备配置] 获取 ConfigAttribute 失败"); + continue; + } + paramBytes[i] = configAttribute.id(); + } + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + J8106 j8106 = new J8106(); + j8106.setParams(paramBytes); + return (JTDeviceConfig) jt1078Template.getDeviceSpecifyConfig(phoneNumber, j8106, 20); + } + } + + @Override + public void setConfig(String phoneNumber, JTDeviceConfig config) { + J8103 j8103 = new J8103(); + j8103.setConfig(config); + jt1078Template.setDeviceSpecifyConfig(phoneNumber, j8103, 6); + } + + @Override + public void connectionControl(String phoneNumber, JTDeviceConnectionControl control) { + J8105 j8105 = new J8105(); + j8105.setConnectionControl(control); + jt1078Template.deviceControl(phoneNumber, j8105, 6); + } + + @Override + public void resetControl(String phoneNumber) { + J8105 j8105 = new J8105(); + j8105.setReset(true); + jt1078Template.deviceControl(phoneNumber, j8105, 6); + } + + @Override + public void factoryResetControl(String phoneNumber) { + J8105 j8105 = new J8105(); + j8105.setFactoryReset(true); + jt1078Template.deviceControl(phoneNumber, j8105, 6); + } + + @Override + public JTDeviceAttribute attribute(String phoneNumber) { + J8107 j8107 = new J8107(); + return (JTDeviceAttribute) jt1078Template.deviceAttribute(phoneNumber, j8107, 20); + } + + @Override + public JTPositionBaseInfo queryPositionInfo(String phoneNumber) { + J8201 j8201 = new J8201(); + return (JTPositionBaseInfo) jt1078Template.queryPositionInfo(phoneNumber, j8201, 20); + } + + @Override + public void tempPositionTrackingControl(String phoneNumber, Integer timeInterval, Long validityPeriod) { + J8202 j8202 = new J8202(); + j8202.setTimeInterval(timeInterval); + j8202.setValidityPeriod(validityPeriod); + jt1078Template.tempPositionTrackingControl(phoneNumber, j8202, 20); + } + + @Override + public void confirmationAlarmMessage(String phoneNumber, int alarmPackageNo, JTConfirmationAlarmMessageType alarmMessageType) { + J8203 j8203 = new J8203(); + j8203.setAlarmMessageType(alarmMessageType); + j8203.setAlarmPackageNo(alarmPackageNo); + jt1078Template.confirmationAlarmMessage(phoneNumber, j8203, 6); + } + + @Override + public int linkDetection(String phoneNumber) { + J8204 j8204 = new J8204(); + Object result = jt1078Template.linkDetection(phoneNumber, j8204, 6); + if (result == null) { + return 1; + }else { + return (int) result; + } + } + + @Override + public int textMessage(String phoneNumber, JTTextSign sign, int textType, String content) { + J8300 j8300 = new J8300(); + j8300.setSign(sign); + j8300.setTextType(textType); + j8300.setContent(content); + return (int) jt1078Template.textMessage(phoneNumber, j8300, 6); + } + + @Override + public int telephoneCallback(String phoneNumber, Integer sign, String destPhoneNumber) { + J8400 j8400 = new J8400(); + j8400.setSign(sign); + j8400.setPhoneNumber(destPhoneNumber); + return (int) jt1078Template.telephoneCallback(phoneNumber, j8400, 6); + } + + @Override + public int setPhoneBook(String phoneNumber, int type, List phoneBookContactList) { + J8401 j8401 = new J8401(); + j8401.setType(type); + if (phoneBookContactList != null) { + j8401.setPhoneBookContactList(phoneBookContactList); + } + return (int) jt1078Template.setPhoneBook(phoneNumber, j8401, 6); + } + + @Override + public JTPositionBaseInfo controlDoor(String phoneNumber, Boolean open) { + J8500 j8500 = new J8500(); + JTVehicleControl jtVehicleControl = new JTVehicleControl(); + jtVehicleControl.setControlCarDoor(open ? 1 : 0); + j8500.setVehicleControl(jtVehicleControl); + return (JTPositionBaseInfo) jt1078Template.vehicleControl(phoneNumber, j8500, 20); + } + + @Override + public int setAreaForCircle(int attribute, String phoneNumber, List circleAreaList) { + J8600 j8600 = new J8600(); + j8600.setAttribute(attribute); + j8600.setCircleAreaList(circleAreaList); + return (int) jt1078Template.setAreaForCircle(phoneNumber, j8600, 20); + } + + @Override + public int deleteAreaForCircle(String phoneNumber, List ids) { + J8601 j8601 = new J8601(); + j8601.setIdList(ids); + return (int) jt1078Template.deleteAreaForCircle(phoneNumber, j8601, 20); + } + + @Override + public List queryAreaForCircle(String phoneNumber, List ids) { + J8608 j8608 = new J8608(); + j8608.setType(1); + j8608.setIdList(ids); + return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); + } + + @Override + public int setAreaForRectangle(int attribute, String phoneNumber, List rectangleAreas) { + J8602 j8602 = new J8602(); + j8602.setAttribute(attribute); + j8602.setRectangleAreas(rectangleAreas); + return (int) jt1078Template.setAreaForRectangle(phoneNumber, j8602, 20); + } + + @Override + public int deleteAreaForRectangle(String phoneNumber, List ids) { + J8603 j8603 = new J8603(); + j8603.setIdList(ids); + return (int) jt1078Template.deleteAreaForRectangle(phoneNumber, j8603, 20); + } + + @Override + public List queryAreaForRectangle(String phoneNumber, List ids) { + J8608 j8608 = new J8608(); + j8608.setType(2); + j8608.setIdList(ids); + return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); + } + + @Override + public int setAreaForPolygon(String phoneNumber, JTPolygonArea polygonArea) { + J8604 j8604 = new J8604(); + j8604.setPolygonArea(polygonArea); + return (int) jt1078Template.setAreaForPolygon(phoneNumber, j8604, 20); + } + + @Override + public int deleteAreaForPolygon(String phoneNumber, List ids) { + J8605 j8605 = new J8605(); + j8605.setIdList(ids); + return (int) jt1078Template.deleteAreaForPolygon(phoneNumber, j8605, 20); + } + + @Override + public List queryAreaForPolygon(String phoneNumber, List ids) { + J8608 j8608 = new J8608(); + j8608.setType(3); + j8608.setIdList(ids); + return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); + } + + @Override + public int setRoute(String phoneNumber, JTRoute route) { + J8606 j8606 = new J8606(); + j8606.setRoute(route); + return (int) jt1078Template.setRoute(phoneNumber, j8606, 20); + } + + @Override + public int deleteRoute(String phoneNumber, List ids) { + J8607 j8607 = new J8607(); + j8607.setIdList(ids); + return (int) jt1078Template.deleteRoute(phoneNumber, j8607, 20); + } + + @Override + public List queryRoute(String phoneNumber, List ids) { + J8608 j8608 = new J8608(); + j8608.setType(4); + j8608.setIdList(ids); + return (List) jt1078Template.queryAreaOrRoute(phoneNumber, j8608, 20); + } + + @Override + public JTDriverInformation queryDriverInformation(String phoneNumber) { + J8702 j8702 = new J8702(); + return (JTDriverInformation) jt1078Template.queryDriverInformation(phoneNumber, j8702, 20); + } + + @Override + public List shooting(String phoneNumber, JTShootingCommand shootingCommand) { + J8801 j8801 = new J8801(); + j8801.setCommand(shootingCommand); + return (List) jt1078Template.shooting(phoneNumber, j8801, 300); + } + + @Override + public List queryMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand) { + J8802 j8802 = new J8802(); + j8802.setCommand(queryMediaDataCommand); + return (List) jt1078Template.queryMediaData(phoneNumber, j8802, 300); + } + + @Override + public void uploadMediaData(String phoneNumber, JTQueryMediaDataCommand queryMediaDataCommand) { + J8803 j8803 = new J8803(); + j8803.setCommand(queryMediaDataCommand); + jt1078Template.uploadMediaData(phoneNumber, j8803, 10); + } + + @Override + public void record(String phoneNumber, int command, Integer time, Integer save, Integer samplingRate) { + J8804 j8804 = new J8804(); + j8804.setCommond(command); + j8804.setDuration(time); + j8804.setSave(save); + j8804.setSamplingRate(samplingRate); + jt1078Template.record(phoneNumber, j8804, 10); + } + + @Override + public void uploadMediaDataForSingle(String phoneNumber, Long mediaId, Integer delete) { + J8805 j8805 = new J8805(); + j8805.setMediaId(mediaId); + j8805.setDelete(delete); + jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 10); + } + + @Override + public JTMediaAttribute queryMediaAttribute(String phoneNumber) { + J9003 j9003 = new J9003(); + return (JTMediaAttribute) jt1078Template.queryMediaAttribute(phoneNumber, j9003, 300); + } + + @Override + public void changeStreamType(String phoneNumber, Integer channelId, Integer streamType) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + phoneNumber + ":" + channelId; + dynamicTask.stop(playKey); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + if (streamInfo == null) { + log.info("[JT-切换码流类型] 未找到点播信息 phoneNumber: {}, channelId: {}, streamType: {}", phoneNumber, channelId, streamType); + } + log.info("[JT-切换码流类型] phoneNumber: {}, channelId: {}, streamType: {}", phoneNumber, channelId, streamType); + // 发送暂停命令 + J9102 j9102 = new J9102(); + j9102.setChannel(channelId); + j9102.setCommand(1); + j9102.setCloseType(0); + j9102.setStreamType(streamType); + jt1078Template.stopLive(phoneNumber, j9102, 6); + } + + @Override + public PageInfo getChannelList(int page, int count, int deviceId, String query) { + + JTDevice device = getDeviceById(deviceId); + if (device == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备不存在"); + } + PageHelper.startPage(page, count); + List all = jtChannelMapper.selectAll(deviceId, query); + PageInfo jtChannelPageInfo = new PageInfo<>(all); + for (JTChannel jtChannel : jtChannelPageInfo.getList()) { + String playKey = VideoManagerConstants.INVITE_INFO_1078_PLAY + device.getPhoneNumber() + ":" + jtChannel.getChannelId(); + StreamInfo streamInfo = (StreamInfo) redisTemplate.opsForValue().get(playKey); + if (streamInfo != null) { + jtChannel.setStream(streamInfo.getStream()); + } + } + return new PageInfo<>(all); + } + + @Override + @Transactional + public void updateChannel(JTChannel channel) { + channel.setUpdateTime(DateUtil.getNow()); + jtChannelMapper.update(channel); + if (!ObjectUtils.isEmpty(channel.getGbDeviceId())) { + if (channel.getGbId() > 0) { + channelService.update(channel.buildCommonGBChannel()); + }else { + channelService.add(channel.buildCommonGBChannel()); + } + } + } + + @Override + @Transactional + public void addChannel(JTChannel channel) { + JTChannel channelInDb = jtChannelMapper.selectChannelByChannelId(channel.getTerminalDbId(), channel.getChannelId()); + if (channelInDb != null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道已存在"); + } + channel.setCreateTime(DateUtil.getNow()); + channel.setUpdateTime(DateUtil.getNow()); + jtChannelMapper.add(channel); + if (!ObjectUtils.isEmpty(channel.getGbDeviceId())) { + channelService.add(channel.buildCommonGBChannel()); + } + } + + @Override + @Transactional + public void deleteChannelById(Integer id) { + JTChannel jtChannel = jtChannelMapper.selectChannelById(id); + if (jtChannel == null) { + return; + } + if (jtChannel.getGbId() > 0) { + channelService.delete(jtChannel.getGbId()); + } + jtChannelMapper.delete(id); + } + + @Override + public JTDevice getDeviceById(Integer deviceId) { + return jtDeviceMapper.getDeviceById(deviceId); + } + + @Override + public void updateDevicePosition(String phoneNumber, Double longitude, Double latitude) { + JTDevice device = new JTDevice(); + device.setPhoneNumber(phoneNumber); + device.setLongitude(longitude); + device.setLatitude(latitude); + device.setUpdateTime(DateUtil.getNow()); + String key = VideoManagerConstants.INVITE_INFO_1078_POSITION + userSetting.getServerId(); + redisTemplate.opsForList().leftPush(key, device); + } + + @Scheduled(fixedDelay = 1000) + public void positionTask(){ + String key = VideoManagerConstants.INVITE_INFO_1078_POSITION + userSetting.getServerId(); + int count = 1000; + List devices = new ArrayList<>(count); + Long size = redisTemplate.opsForList().size(key); + if (size == null || size == 0) { + return; + } + long readCount = Math.min(count, size); + for (long i = 0L; i < readCount; i++) { + devices.add((JTDevice)redisTemplate.opsForList().rightPop(key)); + } + jtDeviceMapper.batchUpdateDevicePosition(devices); + } + + @Override + public JTChannel getChannelByDbId(Integer id) { + return jtChannelMapper.selectChannelById(id); + } + + + + @Override + public String getRecordTempUrl(String phoneNumber, Integer channelId, String startTime, String endTime, Integer alarmSign, Integer mediaType, Integer streamType, Integer storageType) { + String filePath = UUID.randomUUID().toString(); + + Session session = SessionManager.INSTANCE.get(phoneNumber); + Assert.notNull(session, "连接不存在"); + InetSocketAddress socketAddress = session.getLoadAddress(); + String hostName = socketAddress.getHostName(); + + BaseUser randomUser = ftpUserManager.getRandomUser(); + + log.info("[JT-录像] 下载,设备:{}, 通道: {}, 开始时间: {}, 结束时间: {} 上传IP: {} 等待上传文件路径: {} 用户名: {}, 密码: {} ", + phoneNumber, channelId, startTime, endTime, hostName, filePath, randomUser.getName(), randomUser.getPassword()); + // 文件上传指令 + J9206 j9206 = new J9206(); + j9206.setChannelId(channelId); + j9206.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(startTime)); + j9206.setEndTime(DateUtil.yyyy_MM_dd_HH_mm_ssTo1078(endTime)); + j9206.setServerIp(hostName); + j9206.setPort(ftpSetting.getPort()); + j9206.setUsername(randomUser.getName()); + j9206.setPassword(randomUser.getPassword()); + j9206.setPath(filePath); + + if (mediaType != null) { + j9206.setMediaType(mediaType); + } + if (streamType != null) { + j9206.setStreamType(streamType); + } + if (storageType != null) { + j9206.setStorageType(storageType); + } + if (alarmSign != null) { + j9206.setAlarmSign(alarmSign); + } + downloadManager.addCatch(filePath, phoneNumber, j9206); + return filePath; + } + + + @Override + public void recordDownload(String filePath, ServletOutputStream outputStream) { + JTRecordDownloadCatch downloadCatch = downloadManager.getCatch(filePath); + Assert.notNull(downloadCatch, "地址不存在"); + fileSystemFactory.addOutputStream(filePath, outputStream); + jt1078Template.fileUpload(downloadCatch.getPhoneNumber(), downloadCatch.getJ9206(), 7200); + downloadManager.runDownload(filePath, 2 * 60 * 60); + fileSystemFactory.removeOutputStream(filePath); + + } + + + @Override + public byte[] snap(String phoneNumber, int channelId) { + J8801 j8801 = new J8801(); + + // 设置抓图默认参数 + JTShootingCommand shootingCommand = new JTShootingCommand(); + shootingCommand.setChanelId(channelId); + shootingCommand.setCommand(1); + shootingCommand.setTime(0); + shootingCommand.setSave(0); + shootingCommand.setResolvingPower(0xFF); + shootingCommand.setQuality(1); + shootingCommand.setBrightness(125); + shootingCommand.setContrastRatio(60); + shootingCommand.setSaturation(60); + shootingCommand.setChroma(125); + + j8801.setCommand(shootingCommand); + log.info("[JT-抓图] 设备编号: {}, 通道编号: {}", phoneNumber, channelId); + // 监听文件上传, 存在设备不回复抓图请求或者回复通用回复,导致缺少抓图编号,但是直接上传文件的,此处通过监听文件上传直接获取文件 + + @SuppressWarnings("unchecked") + List ids = (List) jt1078Template.shooting(phoneNumber, j8801, 300); + log.info("[JT-抓图] 抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); + + log.info("[JT-抓图] 请求上传图片,抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); + J8805 j8805 = new J8805(); + j8805.setMediaId(ids.get(0)); + j8805.setDelete(1); + JTMediaEventInfo mediaEventInfo = (JTMediaEventInfo)jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 600); + if (mediaEventInfo == null) { + log.info("[]"); + throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); + } + log.info("[JT-抓图] 图片上传完成,抓图编号: {}, 设备编号: {}, 通道编号: {}", ids.get(0), phoneNumber, channelId); + return mediaEventInfo.getMediaData(); + } + + @Override + public void uploadOneMedia(String phoneNumber, Long mediaId, ServletOutputStream outputStream, boolean delete) { + log.info("[JT-单条存储多媒体数据上传] 媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); + J8805 j8805 = new J8805(); + j8805.setMediaId(mediaId); + j8805.setDelete(delete ? 1 : 0); + log.info("[JT-单条存储多媒体数据上传] 请求上传,媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); + JTMediaEventInfo mediaEventInfo = (JTMediaEventInfo)jt1078Template.uploadMediaDataForSingle(phoneNumber, j8805, 600); + if (mediaEventInfo == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); + } + log.info("[JT-单条存储多媒体数据上传] 图片上传完成,媒体编号: {}, 设备编号: {}", mediaId, phoneNumber); + try { + outputStream.write(mediaEventInfo.getMediaData()); + outputStream.flush(); + } catch (IOException e) { + log.info("[JT-单条存储多媒体数据上传] 数据写入异常,抓图编号: {}, 设备编号: {}", mediaId, phoneNumber, e); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "数据写入异常"); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/session/FtpDownloadManager.java b/src/main/java/com/genersoft/iot/vmp/jt1078/session/FtpDownloadManager.java new file mode 100644 index 000000000..01ce90b97 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/session/FtpDownloadManager.java @@ -0,0 +1,105 @@ +package com.genersoft.iot.vmp.jt1078.session; + +import com.genersoft.iot.vmp.jt1078.bean.JTRecordDownloadCatch; +import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent; +import com.genersoft.iot.vmp.jt1078.proc.response.J9206; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class FtpDownloadManager { + + private final Map downloadCatchMap = new ConcurrentHashMap<>(); + private final DelayQueue downloadCatchQueue = new DelayQueue<>(); + + private final Map> topicSubscribers = new ConcurrentHashMap<>(); + + // 下载过期检查 + @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.SECONDS) + public void downloadCatchCheck(){ + while (!downloadCatchQueue.isEmpty()) { + try { + JTRecordDownloadCatch take = downloadCatchQueue.take(); + downloadCatchMap.remove(take.getPath()); + } catch (InterruptedException e) { + log.error("[下载过期] ", e); + } + } + } + + public void addCatch(String path, String phoneNumber, J9206 j9206) { + JTRecordDownloadCatch downloadCatch = new JTRecordDownloadCatch(); + downloadCatch.setPhoneNumber(phoneNumber); + downloadCatch.setPath(path); + downloadCatch.setJ9206(j9206); + + // 10分钟临时地址无法访问则删除 + downloadCatch.setDelayTime(System.currentTimeMillis() + 10 * 60 * 1000L); + + downloadCatchMap.put(path, downloadCatch); + downloadCatchQueue.add(downloadCatch); + } + + public JTRecordDownloadCatch getCatch(String path) { + return downloadCatchMap.get(path); + } + + @EventListener + public void onApplicationEvent(FtpUploadEvent event) { + if (topicSubscribers.isEmpty()) { + return; + } + topicSubscribers.keySet().forEach(key -> { + if (!event.getFileName().contains(key)) { + return; + } + SynchronousQueue synchronousQueue = topicSubscribers.get(key); + if (synchronousQueue != null) { + synchronousQueue.offer(null); + } + }); + } + + + public Object runDownload(String path, long timeOut) { + SynchronousQueue subscribe = subscribe(path); + if (subscribe == null) { + log.error("[JT-下载] 暂停进程失败"); + return null; + } + try { + return subscribe.poll(timeOut, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("[JT-下载] 暂停进程超时", e); + } finally { + this.unsubscribe(path); + JTRecordDownloadCatch downloadCatch = getCatch(path); + if (downloadCatch != null) { + downloadCatchMap.remove(path); + downloadCatchQueue.remove(downloadCatch); + } + } + return null; + } + + private SynchronousQueue subscribe(String key) { + SynchronousQueue queue = null; + if (!topicSubscribers.containsKey(key)) + topicSubscribers.put(key, queue = new SynchronousQueue<>()); + return queue; + } + + private void unsubscribe(String key) { + topicSubscribers.remove(key); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java b/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java index 5360f1ea5..9c3d13ac1 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java @@ -2,9 +2,14 @@ package com.genersoft.iot.vmp.jt1078.session; import com.genersoft.iot.vmp.jt1078.proc.Header; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.concurrent.atomic.AtomicInteger; /** @@ -24,17 +29,27 @@ public class Session { private final AtomicInteger serialNo = new AtomicInteger(0); // 是否注册成功 + @Getter private boolean registered = false; - // 设备ID - private String devId; + // 设备手机号 + @Getter + private String phoneNumber; + + // 设备手机号 + @Setter + @Getter + private String authenticationCode; // 创建时间 + @Getter private final long creationTime; // 协议版本号 + @Getter private Integer protocolVersion; + @Getter private Header header; protected Session(Channel channel) { @@ -68,46 +83,29 @@ public class Session { * @param devId 设备ID */ public void register(String devId, Integer version, Header header) { - this.devId = devId; + this.phoneNumber = devId; this.registered = true; this.protocolVersion = version; this.header = header; SessionManager.INSTANCE.put(devId, this); } - /** - * 获取设备号 - * - * @return 设备号 - */ - public String getDevId() { - return devId; - } - - - public boolean isRegistered() { - return registered; - } - - public long getCreationTime() { - return creationTime; - } - - public Integer getProtocolVersion() { - return protocolVersion; - } - - public Header getHeader() { - return header; - } - @Override public String toString() { return "[" + - "devId=" + devId + + "phoneNumber=" + phoneNumber + ", reg=" + registered + ", version=" + protocolVersion + ",ip=" + channel.remoteAddress() + ']'; } + + public void unregister() { + channel.close(); + SessionManager.INSTANCE.remove(this.phoneNumber); + } + + public InetSocketAddress getLoadAddress() { + return (InetSocketAddress)channel.localAddress(); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java b/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java index 859e1f85c..48979f81e 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java @@ -20,7 +20,7 @@ public enum SessionManager { INSTANCE; // 用与消息的缓存 - private final Map> topicSubscribers = new ConcurrentHashMap<>(); + private final Map> topicSubscribers = new ConcurrentHashMap<>(); // session的缓存 private final Map sessionMap; @@ -56,7 +56,7 @@ public enum SessionManager { * @param clientId 设备ID * @param newSession session */ - protected void put(Object clientId, Session newSession) { + void put(Object clientId, Session newSession) { sessionMap.put(clientId, newSession); } @@ -65,22 +65,22 @@ public enum SessionManager { * 发送同步消息,接收响应 * 默认超时时间6秒 */ - public String request(Cmd cmd) { + public Object request(Cmd cmd) { // 默认6秒 int timeOut = 6000; return request(cmd, timeOut); } - public String request(Cmd cmd, Integer timeOut) { - Session session = this.get(cmd.getDevId()); + public Object request(Cmd cmd, Integer timeOut) { + Session session = this.get(cmd.getPhoneNumber()); if (session == null) { - log.error("DevId: {} not online!", cmd.getDevId()); + log.error("DevId: {} not online!", cmd.getPhoneNumber()); return null; } - String requestKey = requestKey(cmd.getDevId(), cmd.getRespId(), cmd.getPackageNo()); - SynchronousQueue subscribe = subscribe(requestKey); + String requestKey = requestKey(cmd.getPhoneNumber(), cmd.getRespId(), cmd.getPackageNo()); + SynchronousQueue subscribe = subscribe(requestKey); if (subscribe == null) { - log.error("DevId: {} key:{} send repaid", cmd.getDevId(), requestKey); + log.error("DevId: {} key:{} send repaid", cmd.getPhoneNumber(), requestKey); return null; } session.writeObject(cmd); @@ -94,33 +94,58 @@ public enum SessionManager { return null; } - public Boolean response(String devId, String respId, Long responseNo, String data) { + public Boolean response(String devId, String respId, Long responseNo, Object data) { String requestKey = requestKey(devId, respId, responseNo); - SynchronousQueue queue = topicSubscribers.get(requestKey); - if (queue != null) { - try { - return queue.offer(data, 2, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.error("{}", e.getMessage(), e); + + boolean result = false; + if (responseNo == null) { + for (String key : topicSubscribers.keySet()) { + if (key.startsWith(requestKey)) { + SynchronousQueue queue = topicSubscribers.get(key); + if (queue != null) { + result = true; + try { + queue.offer(data, 2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } + } + } + } + }else { + SynchronousQueue queue = topicSubscribers.get(requestKey); + if (queue != null) { + result = true; + try { + queue.offer(data, 2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } } } - log.warn("Not find response,key:{} data:{} ", requestKey, data); - return false; + if (!result) { + log.warn("Not find response,key:{} data:{} ", requestKey, data); + } + return result; } private void unsubscribe(String key) { topicSubscribers.remove(key); } - private SynchronousQueue subscribe(String key) { - SynchronousQueue queue = null; + private SynchronousQueue subscribe(String key) { + SynchronousQueue queue = null; if (!topicSubscribers.containsKey(key)) - topicSubscribers.put(key, queue = new SynchronousQueue()); + topicSubscribers.put(key, queue = new SynchronousQueue<>()); return queue; } private String requestKey(String devId, String respId, Long requestNo) { - return String.join("_", devId.replaceFirst("^0*", ""), respId, requestNo.toString()); + return String.join("_", devId.replaceFirst("^0*", ""), respId, requestNo == null?"":requestNo.toString()); + } + public void remove(String devId) { + sessionMap.remove(devId); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/util/BCDUtil.java b/src/main/java/com/genersoft/iot/vmp/jt1078/util/BCDUtil.java new file mode 100644 index 000000000..b76786e6e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/util/BCDUtil.java @@ -0,0 +1,64 @@ +package com.genersoft.iot.vmp.jt1078.util; + +/** + * BCD码转换 + */ +public class BCDUtil { + + public static String transform(byte[] bytes) { + if (bytes.length == 0) { + return null; + } + // BCD + StringBuilder stringBuffer = new StringBuilder(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) { + // 每次取出四位的值,一个byte是八位,第一取出高四位,第二次取出低四位, + // 这里也可以先 & 0xf0再右移4位,0xf0二进制为11110000,与运算后,可以得到高4位是值,低四位清零的结果 + stringBuffer.append((byte) ((bytes[i] >>> 4 & 0xf))); + stringBuffer.append((byte) (bytes[i] & 0x0f)); + } + return stringBuffer.toString(); + } + + /** + * 字符串转BCD码 + * 来自: https://www.cnblogs.com/ranandrun/p/BCD.html + * @param asc ASCII字符串 + * @return BCD + */ + public static byte[] strToBcd(String asc) { + int len = asc.length(); + int mod = len % 2; + if (mod != 0) { + asc = "0" + asc; + len = asc.length(); + } + byte abt[] = new byte[len]; + if (len >= 2) { + len >>= 1; + } + byte bbt[] = new byte[len]; + abt = asc.getBytes(); + int j, k; + for (int p = 0; p < asc.length() / 2; p++) { + if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) { + j = abt[2 * p] - '0'; + } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { + j = abt[2 * p] - 'a' + 0x0a; + } else { + j = abt[2 * p] - 'A' + 0x0a; + } + if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) { + k = abt[2 * p + 1] - '0'; + } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { + k = abt[2 * p + 1] - 'a' + 0x0a; + } else { + k = abt[2 * p + 1] - 'A' + 0x0a; + } + int a = (j << 4) + k; + byte b = (byte) a; + bbt[p] = b; + } + return bbt; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLHttpHookListener.java new file mode 100644 index 000000000..ade53797e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLHttpHookListener.java @@ -0,0 +1,385 @@ +package com.genersoft.iot.vmp.media.abl; + +import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.service.IDeviceService; +import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; +import com.genersoft.iot.vmp.gb28181.service.IPlayService; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; +import com.genersoft.iot.vmp.media.abl.bean.hook.*; +import com.genersoft.iot.vmp.media.abl.event.HookAblServerKeepaliveEvent; +import com.genersoft.iot.vmp.media.abl.event.HookAblServerStartEvent; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; +import com.genersoft.iot.vmp.media.event.media.*; +import com.genersoft.iot.vmp.media.service.IMediaServerService; +import com.genersoft.iot.vmp.media.zlm.dto.hook.HookResult; +import com.genersoft.iot.vmp.media.zlm.dto.hook.HookResultForOnPublish; +import com.genersoft.iot.vmp.service.*; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.ObjectUtils; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * ABL 的hook事件监听 + */ +@RestController +@RequestMapping("/index/hook/abl") +public class ABLHttpHookListener { + + private final static Logger logger = LoggerFactory.getLogger(ABLHttpHookListener.class); + + @Autowired + private ABLRESTfulUtils ablresTfulUtils; + + @Autowired + private ISIPCommanderForPlatform commanderFroPlatform; + + @Autowired + private AudioBroadcastManager audioBroadcastManager; + + @Autowired + private IPlayService playService; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IInviteStreamService inviteStreamService; + + @Autowired + private IDeviceService deviceService; + + @Autowired + private IMediaServerService mediaServerService; + + + @Autowired + private IMediaService mediaService; + + + @Autowired + private UserSetting userSetting; + + @Autowired + private SSRCFactory ssrcFactory; + + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + /** + * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 + */ + @ResponseBody + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") + public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveABLHookParam param) { + try { + HookAblServerKeepaliveEvent event = new HookAblServerKeepaliveEvent(this); + MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServerItem != null) { + event.setMediaServerItem(mediaServerItem); + applicationEventPublisher.publishEvent(event); + } + }catch (Exception e) { + logger.info("[ZLM-HOOK-心跳] 发送通知失败 ", e); + } + return HookResult.SUCCESS(); + } + + /** + * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 + */ + @ResponseBody + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") + public HookResult onPlay(@RequestBody OnPlayABLHookParam param) { + + MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServer == null) { + return new HookResultForOnPublish(0, "success"); + } + + Map paramMap = urlParamToMap(param.getParams()); + // 对于播放流进行鉴权 + boolean authenticateResult = mediaService.authenticatePlay(param.getApp(), param.getStream(), paramMap.get("callId")); + if (!authenticateResult) { + logger.info("[ABL HOOK] 播放鉴权 失败:{}->{}", param.getMediaServerId(), param); + ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); + + } + logger.info("[ABL HOOK] 播放鉴权成功:{}->{}", param.getMediaServerId(), param); + return HookResult.SUCCESS(); + } + + /** + * rtsp/rtmp/rtp推流鉴权事件。 + */ + @ResponseBody + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") + public HookResult onPublish(@RequestBody OnPublishABLHookParam param) { + + + logger.info("[ABL HOOK] 推流鉴权:{}->{}/{}?{}", param.getMediaServerId(), param.getApp(), param.getStream(), param.getParams()); + // TODO 加快处理速度 + + MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServer == null) { + return new HookResultForOnPublish(0, "success"); + } + + try { + ResultForOnPublish resultForOnPublish = mediaService.authenticatePublish(mediaServer, param.getApp(), param.getStream(), param.getParams()); + if (resultForOnPublish == null) { + logger.info("[ABL HOOK]推流鉴权 拒绝 响应:{}->{}", param.getMediaServerId(), param); + ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); + } + }catch (ControllerException e) { + ablresTfulUtils.closeStreams(mediaServer, param.getApp(), param.getStream()); + } + return HookResult.SUCCESS(); + } + + /** + * 如果某一个码流进行MP4录像(enable_mp4=1),会触发录像进度通知事件 + */ + @ResponseBody + @PostMapping(value = "/on_record_progress", produces = "application/json;charset=UTF-8") + public HookResult onRecordProgress(@RequestBody OnRecordProgressABLHookParam param) { + + + logger.info("[ABL HOOK] 录像进度通知:{}->{}/{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream(), param.getCurrentFileDuration(), param.getTotalVideoDuration()); + + try { + MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServerItem != null) { + MediaRecordProcessEvent event = MediaRecordProcessEvent.getInstance(this, param, mediaServerItem); + event.setMediaServer(mediaServerItem); + applicationEventPublisher.publishEvent(event); + } + }catch (Exception e) { + logger.info("[ZLM-HOOK-录像进度通知] 发送通知失败 ", e); + } + return HookResult.SUCCESS(); + } + + /** + * 当代理拉流、国标接入等等 码流不到达时会发出 码流不到达的事件通知 + */ + @ResponseBody + @PostMapping(value = "/on_stream_not_arrive", produces = "application/json;charset=UTF-8") + public HookResult onStreamNotArrive(@RequestBody ABLHookParam param) { + + + logger.info("[ABL HOOK] 码流不到达通知:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + try { + if ("rtp".equals(param.getApp())) { + return HookResult.SUCCESS(); + } + MediaRtpServerTimeoutEvent event = new MediaRtpServerTimeoutEvent(this); + MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServerItem != null) { + event.setMediaServer(mediaServerItem); + event.setApp("rtp"); + applicationEventPublisher.publishEvent(event); + } + }catch (Exception e) { + logger.info("[ABL-HOOK-码流不到达通知] 发送通知失败 ", e); + } + + return HookResult.SUCCESS(); + } + + /** + * 如果某一个码流进行MP4录像(enable_mp4=1),当某个MP4文件被删除会触发该事件通知 + */ + @ResponseBody + @PostMapping(value = "/on_delete_record_mp4", produces = "application/json;charset=UTF-8") + public HookResult onDeleteRecordMp4(@RequestBody OnRecordMp4ABLHookParam param) { + + + logger.info("[ABL HOOK] MP4文件被删除通知:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + + + return HookResult.SUCCESS(); + } + + + /** + * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 + */ + @ResponseBody + @PostMapping(value = "/on_stream_arrive", produces = "application/json;charset=UTF-8") + public HookResult onStreamArrive(@RequestBody OnStreamArriveABLHookParam param) { + + MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServer == null) { + return HookResult.SUCCESS(); + } + + logger.info("[ABL HOOK] 码流到达, {}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + MediaArrivalEvent mediaArrivalEvent = MediaArrivalEvent.getInstance(this, param, mediaServer); + applicationEventPublisher.publishEvent(mediaArrivalEvent); + return HookResult.SUCCESS(); + } + + /** + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 + */ + @ResponseBody + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") + public JSONObject onStreamNoneReader(@RequestBody ABLHookParam param) { + + logger.info("[ABL HOOK]流无人观看:{}->{}/{}", param.getMediaServerId(), + param.getApp(), param.getStream()); + JSONObject ret = new JSONObject(); + + boolean close = mediaService.closeStreamOnNoneReader(param.getMediaServerId(), param.getApp(), param.getStream(), null); + ret.put("code", close); + return ret; + } + + /** + * 当播放一个url,如果不存在时,会发出一个消息通知 + */ + @ResponseBody + @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") + public HookResult onStreamNotFound(@RequestBody ABLHookParam param) { + + logger.info("[ABL HOOK] 流未找到:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); + if (!userSetting.getAutoApplyPlay() || mediaServer == null) { + return HookResult.SUCCESS(); + } + MediaNotFoundEvent mediaNotFoundEvent = MediaNotFoundEvent.getInstance(this, param, mediaServer); + applicationEventPublisher.publishEvent(mediaNotFoundEvent); + return HookResult.SUCCESS(); + } + + /** + * ABLMediaServer启动时会发送上线通知 + */ + @ResponseBody + @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") + public HookResult onServerStarted(HttpServletRequest request, @RequestBody OnServerStaredABLHookParam param) { + + logger.info("[ABL HOOK] 启动 " + param.getMediaServerId()); + try { + HookAblServerStartEvent event = new HookAblServerStartEvent(this); + MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServerItem != null) { + event.setMediaServerItem(mediaServerItem); + applicationEventPublisher.publishEvent(event); + } + }catch (Exception e) { + logger.info("[ABL-HOOK-启动] 发送通知失败 ", e); + } + + return HookResult.SUCCESS(); + } + + /** + * TODO 发送rtp(startSendRtp)被动关闭时回调 + */ +// @ResponseBody +// @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") +// public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { +// +// logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); +// +// // 查找对应的上级推流,发送停止 +// if (!"rtp".equals(param.getApp())) { +// return HookResult.SUCCESS(); +// } +// try { +// MediaSendRtpStoppedEvent event = new MediaSendRtpStoppedEvent(this); +// MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); +// if (mediaServerItem != null) { +// event.setMediaServer(mediaServerItem); +// applicationEventPublisher.publishEvent(event); +// } +// }catch (Exception e) { +// logger.info("[ZLM-HOOK-rtp发送关闭] 发送通知失败 ", e); +// } +// +// return HookResult.SUCCESS(); +// } + + /** + * TODO 录像完成事件 + */ + @ResponseBody + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") + public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4ABLHookParam param) { + logger.info("[ABL HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFileName()); + + try { + MediaServer mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServerItem != null) { + MediaRecordMp4Event event = MediaRecordMp4Event.getInstance(this, param, mediaServerItem); + event.setMediaServer(mediaServerItem); + applicationEventPublisher.publishEvent(event); + } + }catch (Exception e) { + logger.info("[ZLM-HOOK-rtpServer收流超时] 发送通知失败 ", e); + } + + return HookResult.SUCCESS(); + } + + /** + * 当某一路码流断开时会发送通知 + */ + @ResponseBody + @PostMapping(value = "/on_stream_disconnect", produces = "application/json;charset=UTF-8") + public HookResult onRecordMp4(HttpServletRequest request, @RequestBody ABLHookParam param) { + logger.info("[ABL HOOK] 码流断开事件, {}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); + + MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId()); + if (mediaServer == null) { + return HookResult.SUCCESS(); + } + + MediaDepartureEvent mediaDepartureEvent = MediaDepartureEvent.getInstance(this, param, mediaServer); + applicationEventPublisher.publishEvent(mediaDepartureEvent); + + return HookResult.SUCCESS(); + } + + private Map urlParamToMap(String params) { + HashMap map = new HashMap<>(); + if (ObjectUtils.isEmpty(params)) { + return map; + } + String[] paramsArray = params.split("&"); + if (paramsArray.length == 0) { + return map; + } + for (String param : paramsArray) { + String[] paramArray = param.split("="); + if (paramArray.length == 2) { + map.put(paramArray[0], paramArray[1]); + } + } + return map; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java new file mode 100644 index 000000000..ab24c23eb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java @@ -0,0 +1,539 @@ +package com.genersoft.iot.vmp.media.abl; + +import com.alibaba.fastjson2.JSONArray; +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.SipConfig; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; +import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; +import com.genersoft.iot.vmp.media.abl.bean.ABLMedia; +import com.genersoft.iot.vmp.media.abl.bean.ABLResult; +import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; +import com.genersoft.iot.vmp.media.bean.MediaInfo; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.bean.RecordInfo; +import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; +import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; +import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service("abl") +public class ABLMediaNodeServerService implements IMediaNodeServerService { + + private final static Logger logger = LoggerFactory.getLogger(ABLMediaNodeServerService.class); + + @Autowired + private ABLRESTfulUtils ablresTfulUtils; + + @Autowired + private SipConfig sipConfig; + + @Autowired + private UserSetting userSetting; + + @Autowired + private CloudRecordServiceMapper cloudRecordServiceMapper; + + @Autowired + private IInviteStreamService inviteStreamService; + + @Override + public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { + return false; + } + + @Override + public int createRTPServer(MediaServer mediaServer, String stream, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { + Boolean recordSip = userSetting.getRecordSip(); + return ablresTfulUtils.openRtpServer(mediaServer, "rtp", stream, 96, port, tcpMode, disableAudio?1:0, recordSip, false); + } + + @Override + public void closeRtpServer(MediaServer mediaServer, String streamId, CommonCallback callback) { + if (mediaServer == null) { + return; + } + ABLResult result = ablresTfulUtils.closeStreams(mediaServer, "rtp", streamId); + logger.info("关闭RTP Server " + result); + if (result.getCode() != 0) { + logger.error("[closeRtpServer] 失败: {}", result.getMemo()); + } + } + + @Override + public int createJTTServer(MediaServer mediaServer, String stream, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode) { + Boolean recordSip = userSetting.getRecordSip(); + return ablresTfulUtils.openRtpServer(mediaServer, "1078", stream, 96, port, tcpMode, disableAudio?1:0, recordSip, true); + } + + @Override + public void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback) { + if (mediaServer == null) { + return; + } + ABLResult result = ablresTfulUtils.closeStreams(mediaServer, "1078", streamId); + logger.info("关闭JT-RTP Server " + result); + if (result.getCode() != 0) { + logger.error("[JT-closeRtpServer] 失败: {}", result.getMemo()); + } + } + + @Override + public void closeStreams(MediaServer mediaServer, String app, String streamId) { + ABLResult result = ablresTfulUtils.closeStreams(mediaServer, app, streamId); + if (result.getCode() != 0) { + logger.error("[closeStreams] 失败: {}", result.getMemo()); + } + } + + @Override + public Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String streamId, String ssrc) { + return null; + } + + @Override + public boolean checkNodeId(MediaServer mediaServerItem) { + logger.warn("[abl-checkNodeId] 未实现"); + return false; + } + + @Override + public void online(MediaServer mediaServerItem) { + logger.warn("[abl-online] 未实现"); + } + + @Override + public MediaServer checkMediaServer(String ip, int port, String secret) { + MediaServer mediaServer = new MediaServer(); + mediaServer.setIp(ip); + mediaServer.setHttpPort(port); + mediaServer.setSecret(secret); + ABLResult result = ablresTfulUtils.getServerConfig(mediaServer); + JSONArray data = result.getParams(); + if (data != null && !data.isEmpty()) { + AblServerConfig config = AblServerConfig.getInstance(data); + config.setServerIp(ip); + config.setHttpServerPort(port); + return new MediaServer(config, sipConfig.getIp()); + } + return null; + } + + @Override + public boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) { + // TODO 需要记录开始发流返回的KEY,暂不做实现 + logger.warn("[abl-stopSendRtp] 未实现"); +// ablresTfulUtils.stopSendRtp() + return false; + } + + @Override + public boolean deleteRecordDirectory(MediaServer mediaServerItem, String app, String stream, String date, String fileName) { + logger.warn("[abl-deleteRecordDirectory] 未实现"); + return false; + } + + @Override + public List getMediaList(MediaServer mediaServer, String app, String stream, String callId) { + ABLResult result = ablresTfulUtils.getMediaList(mediaServer, app, stream); + if (result.getCode() != 0) { + return null; + } + if (result.getMediaList() == null || result.getMediaList().isEmpty()) { + return new ArrayList<>(); + } + List streamInfoList = new ArrayList<>(); + for (int i = 0; i < result.getMediaList().size(); i++) { + ABLMedia ablMedia = result.getMediaList().get(i); + MediaInfo mediaInfo = MediaInfo.getInstance(ablMedia, mediaServer); + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, null, callId, true); + if (streamInfo != null) { + streamInfoList.add(streamInfo); + } + } + return streamInfoList; + } + + @Override + public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, + String addr, String callId, boolean isPlay) { + StreamInfo streamInfoResult = new StreamInfo(); + streamInfoResult.setStream(stream); + streamInfoResult.setApp(app); + if (addr == null) { + addr = mediaServer.getStreamIp(); + } + + streamInfoResult.setIp(addr); + if (mediaInfo != null) { + streamInfoResult.setServerId(mediaInfo.getServerId()); + }else { + streamInfoResult.setServerId(userSetting.getServerId()); + } + + streamInfoResult.setMediaServer(mediaServer); + Map param = new HashMap<>(); + if (!ObjectUtils.isEmpty(callId)) { + param.put("callId", callId); + } + if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { + param.put("originTypeStr", mediaInfo.getOriginTypeStr()); + } + StringBuilder callIdParamBuilder = new StringBuilder(); + if (!param.isEmpty()) { + callIdParamBuilder.append("?"); + for (Map.Entry entry : param.entrySet()) { + callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); + callIdParamBuilder.append("&"); + } + callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); + } + + String callIdParam = callIdParamBuilder.toString(); + + streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); + streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); + + String flvFile = String.format("%s/%s.flv%s", app, stream, callIdParam); + if ((mediaServer.getFlvPort() & 1) == 1) { + // 奇数端口 默认ssl端口 + streamInfoResult.setFlv(addr, null, mediaServer.getFlvPort(), flvFile); + }else { + streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),null, flvFile); + } + if ((mediaServer.getWsFlvPort() & 1) == 1) { + // 奇数端口 默认ssl端口 + streamInfoResult.setWsFlv(addr, null, mediaServer.getWsFlvPort(), flvFile); + }else { + streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),null, flvFile); + } + String mp4File = String.format("%s/%s.mp4%s", app, stream, callIdParam); + if ((mediaServer.getMp4Port() & 1) == 1) { + // 奇数端口 默认ssl端口 + streamInfoResult.setFmp4(addr, null, mediaServer.getMp4Port(), mp4File); + }else { + streamInfoResult.setFmp4(addr, mediaServer.getMp4Port(), null, mp4File); + } + + streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); + + streamInfoResult.setMediaInfo(mediaInfo); + + if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { + String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); + mediaServer.setTranscodeSuffix(null); + StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); + streamInfoResult.setTranscodeStream(transcodeStreamInfo); + } + return streamInfoResult; + } + + @Override + public Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String stream) { + logger.warn("[abl-connectRtpServer] 未实现"); + return null; + } + + @Override + public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { + ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName); + } + + @Override + public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) { + ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, app, stream); + if (ablResult.getCode() != 0) { + return null; + } + if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) { + return null; + } + return MediaInfo.getInstance(ablResult.getMediaList().get(0), mediaServer); + } + + @Override + public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) { + ABLResult ablResult = ablresTfulUtils.pauseRtpServer(mediaServer, streamKey); + return ablResult.getCode() == 0; + } + + @Override + public Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) { + ABLResult ablResult = ablresTfulUtils.resumeRtpServer(mediaServer, streamKey); + return ablResult.getCode() == 0; + } + + @Override + public String getFfmpegCmd(MediaServer mediaServer, String cmdKey) { + return ""; + } + + @Override + public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) { + ABLResult ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey); + return ablResult.getCode() == 0; + } + + @Override + public Boolean delStreamProxy(MediaServer mediaServer, String streamKey) { + ABLResult ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey); + return ablResult.getCode() == 0; + } + + @Override + public Map getFFmpegCMDs(MediaServer mediaServer) { + return new HashMap<>(); + } + + // 接受进度通知 +// @EventListener +// public void onApplicationEvent(MediaRecordProcessEvent event) { +// CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName()); +// if (cloudRecordItem == null) { +// cloudRecordItem = CloudRecordItem.getInstance(event); +// cloudRecordItem.setStartTime(event.getStartTime()); +// cloudRecordItem.setEndTime(event.getEndTime()); +// cloudRecordServiceMapper.add(cloudRecordItem); +// }else { +// cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis()); +// } +// } + @EventListener + public void onApplicationEvent(MediaRecordMp4Event event) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream()); + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { + return; + } + List cloudRecordItemList = cloudRecordServiceMapper.getList(null, event.getApp(), event.getStream(), + null, null, null, null, null, null); + if (cloudRecordItemList.isEmpty()) { + return; + } + long startTime = cloudRecordItemList.get(cloudRecordItemList.size() - 1).getStartTime(); + long endTime = cloudRecordItemList.get(0).getEndTime(); + ABLResult ablResult = ablresTfulUtils.queryRecordList(event.getMediaServer(), event.getApp(), event.getStream(), DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(startTime), + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(endTime)); + if (ablResult.getCode() != 0) { + return; + } + if (ablResult.getUrl() == null) { + return; + } + String download = ablResult.getUrl().getDownload(); + DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); + downloadFileInfo.setHttpPath(download); + downloadFileInfo.setHttpsPath(download); + inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + } + + @Override + public Long updateDownloadProcess(MediaServer mediaServer, String app, String stream) { + List list = cloudRecordServiceMapper.getList(null, app, stream, null, + null, null, null, null, null); + if (list.isEmpty()) { + return null; + } + long downloadProcess = 0L; + for (CloudRecordItem cloudRecordItem : list) { + downloadProcess += (long) cloudRecordItem.getTimeLen(); + } + return downloadProcess; + } + + @Override + public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { + + ABLResult result = ablresTfulUtils.addStreamProxy(mediaServer, app, stream, url, !enableAudio, enableMp4, rtpType, timeout); + if (result.getCode() != 0) { + return WVPResult.fail(ErrorCode.ERROR100.getCode(), result.getMemo()); + }else { + return WVPResult.success(result.getKey()); + } + } + + @Override + public Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { + logger.warn("[abl-startSendRtpPassive] 未实现"); + return 0; + } + + @Override + public Integer startSendRtpTalk(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { + logger.warn("[abl-startSendRtpTalk] 未实现"); + return 0; + } + + @Override + public void startSendRtpStream(MediaServer mediaServer, SendRtpInfo sendRtpItem) { + logger.warn("[abl-startSendRtpStream] 未实现"); + } + + @Override + public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { + + MediaInfo mediaInfo = getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream()); + + if (mediaInfo != null) { + closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream()); + } + + ABLResult ablResult = null; + if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())){ + if (streamProxy.getTimeout() == 0) { + streamProxy.setTimeout(15); + } + ablResult = ablresTfulUtils.addFFmpegProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), + !streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); + }else { + ablResult = ablresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), + streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); + } + if (ablResult.getCode() != 0) { + throw new ControllerException(ablResult.getCode(), ablResult.getMemo()); + }else { + String key = ablResult.getKey(); + if (key == null) { + throw new ControllerException(ablResult.getCode(), "代理结果异常: " + ablResult); + }else { + return key; + } + } + } + + @Override + public void stopProxy(MediaServer mediaServer, String streamKey, String type) { + ABLResult ablResult = null; + if ("ffmpeg".equalsIgnoreCase(type)){ + ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey); + }else { + ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey); + } + if (ablResult.getCode() != 0) { + throw new ControllerException(ablResult.getCode(), ablResult.getMemo()); + } + } + + @Override + public List listRtpServer(MediaServer mediaServer) { + ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, "rtp", null); + if (ablResult.getCode() != 0) { + return null; + } + if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + for (int i = 0; i < ablResult.getMediaList().size(); i++) { + ABLMedia ablMedia = ablResult.getMediaList().get(i); + result.add(ablMedia.getStream()); + } + return result; + } + + @Override + public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { + String buildStream = String.format("%s__ReplayFMP4RecordFile__%s", stream, fileName); + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, buildStream, null, null, null, true); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + } + + @Override + public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { + // 解析为 LocalDate + LocalDate localDate = LocalDate.parse(date, DateUtil.DateFormatter); + LocalDateTime startOfDay = localDate.atStartOfDay(); + LocalDateTime endOfDay = localDate.atTime(23, 59,59, 999); + String startTime = DateUtil.urlFormatter.format(startOfDay); + String endTime = DateUtil.urlFormatter.format(endOfDay); + + ABLResult ablResult = ablresTfulUtils.queryRecordList(mediaServer, app, stream, startTime, endTime); + if (ablResult.getCode() != 0) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), ablResult.getMemo()); + } + String resultApp = ablResult.getApp(); + String resultStream = ablResult.getStream(); + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, resultApp, resultStream, null, null,null, true); + streamInfo.setKey(ablResult.getKey()); + if (callback != null) { + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + } + } + + @Override + public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { + ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "seek", stamp/1000 + ""); + if (ablResult.getCode() != 0) { + log.warn("[abl-seek] 失败:{}", ablResult.getMemo()); + } + } + + @Override + public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { + ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "scale", speed + ""); + if (ablResult.getCode() != 0) { + log.warn("[abl-倍速] 失败:{}", ablResult.getMemo()); + } + } + + @Override + public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) { + // 将filePath作为独立参数传入,避免%符号解析问题 + String pathTemplate = "%s://%s:%s/%s/%s__ReplayFMP4RecordFile__%s?download_speed=16"; + + DownloadFileInfo info = new DownloadFileInfo(); + if ((mediaServer.getMp4Port() & 1) == 1) { + info.setHttpsPath( + String.format( + pathTemplate, + "https", + mediaServer.getStreamIp(), + mediaServer.getMp4Port(), + recordInfo.getApp(), + recordInfo.getStream(), + recordInfo.getFileName() + ) + ); + }else { + info.setHttpPath( + String.format( + pathTemplate, + "http", + mediaServer.getStreamIp(), + mediaServer.getMp4Port(), + recordInfo.getApp(), + recordInfo.getStream(), + recordInfo.getFileName() + ) + ); + } + return info; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaServerStatusManger.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaServerStatusManger.java new file mode 100644 index 000000000..a820851dd --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaServerStatusManger.java @@ -0,0 +1,358 @@ +package com.genersoft.iot.vmp.media.abl; + +import com.alibaba.fastjson2.JSONArray; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.media.abl.bean.ABLResult; +import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; +import com.genersoft.iot.vmp.media.abl.bean.ConfigKeyId; +import com.genersoft.iot.vmp.media.abl.event.HookAblServerKeepaliveEvent; +import com.genersoft.iot.vmp.media.abl.event.HookAblServerStartEvent; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; +import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; +import com.genersoft.iot.vmp.media.service.IMediaServerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 管理zlm流媒体节点的状态 + */ +@Component +public class ABLMediaServerStatusManger { + + private final static Logger logger = LoggerFactory.getLogger(ABLMediaServerStatusManger.class); + + private final Map offlineABLPrimaryMap = new ConcurrentHashMap<>(); + private final Map offlineAblsecondaryMap = new ConcurrentHashMap<>(); + private final Map offlineAblTimeMap = new ConcurrentHashMap<>(); + + @Autowired + private ABLRESTfulUtils ablResTfulUtils; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private DynamicTask dynamicTask; + + @Value("${server.ssl.enabled:false}") + private boolean sslEnabled; + + @Value("${server.port}") + private Integer serverPort; + + @Autowired + private UserSetting userSetting; + + private final String type = "abl"; + + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaServerChangeEvent event) { + if (event.getMediaServerItemList() == null + || event.getMediaServerItemList().isEmpty()) { + return; + } + for (MediaServer mediaServer : event.getMediaServerItemList()) { + if (!type.equals(mediaServer.getType())) { + continue; + } + logger.info("[ABL-添加待上线节点] ID:" + mediaServer.getId()); + offlineABLPrimaryMap.put(mediaServer.getId(), mediaServer); + offlineAblTimeMap.put(mediaServer.getId(), System.currentTimeMillis()); + } + execute(); + } + + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(HookAblServerStartEvent event) { + if (event.getMediaServerItem() == null + || !type.equals(event.getMediaServerItem().getType()) + || event.getMediaServerItem().isStatus()) { + return; + } + MediaServer serverItem = mediaServerService.getOne(event.getMediaServerItem().getId()); + if (serverItem == null) { + return; + } + logger.info("[ABL-HOOK事件-服务启动] ID:" + event.getMediaServerItem().getId()); + online(serverItem, null); + } + + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(HookAblServerKeepaliveEvent event) { + if (event.getMediaServerItem() == null) { + return; + } + MediaServer serverItem = mediaServerService.getOne(event.getMediaServerItem().getId()); + if (serverItem == null) { + return; + } + logger.info("[ABL-HOOK事件-心跳] ID:" + event.getMediaServerItem().getId()); + online(serverItem, null); + } + + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaServerDeleteEvent event) { + if (event.getMediaServer() == null) { + return; + } + logger.info("[ABL-节点被移除] ID:" + event.getMediaServer().getServerId()); + offlineABLPrimaryMap.remove(event.getMediaServer().getServerId()); + offlineAblsecondaryMap.remove(event.getMediaServer().getServerId()); + offlineAblTimeMap.remove(event.getMediaServer().getServerId()); + } + + @Scheduled(fixedDelay = 10*1000) //每隔10秒检查一次 + public void execute(){ + // 初次加入的离线节点会在30分钟内,每间隔十秒尝试一次,30分钟后如果仍然没有上线,则每隔30分钟尝试一次连接 + if (offlineABLPrimaryMap.isEmpty() && offlineAblsecondaryMap.isEmpty()) { + return; + } + if (!offlineABLPrimaryMap.isEmpty()) { + for (MediaServer mediaServerItem : offlineABLPrimaryMap.values()) { + if (offlineAblTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { + offlineAblsecondaryMap.put(mediaServerItem.getId(), mediaServerItem); + offlineABLPrimaryMap.remove(mediaServerItem.getId()); + continue; + } + logger.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem); + AblServerConfig ablServerConfig = null; + if (ablResult.getCode() != 0) { + logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + continue; + } + JSONArray params = ablResult.getParams(); + + if (params == null || params.isEmpty()) { + logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + }else { + ablServerConfig = AblServerConfig.getInstance(params); + initPort(mediaServerItem, ablServerConfig); + online(mediaServerItem, ablServerConfig); + } + } + } + if (!offlineAblsecondaryMap.isEmpty()) { + for (MediaServer mediaServerItem : offlineAblsecondaryMap.values()) { + if (offlineAblTimeMap.get(mediaServerItem.getId()) < System.currentTimeMillis() - 30*60*1000) { + continue; + } + logger.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem); + AblServerConfig ablServerConfig = null; + if (ablResult.getCode() != 0) { + logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); + continue; + } + JSONArray params = ablResult.getParams(); + if (params == null || params.isEmpty()) { + logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); + offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); + }else { + ablServerConfig = AblServerConfig.getInstance(params); + initPort(mediaServerItem, ablServerConfig); + online(mediaServerItem, ablServerConfig); + } + } + } + } + + private void online(MediaServer mediaServer, AblServerConfig config) { + offlineABLPrimaryMap.remove(mediaServer.getId()); + offlineAblsecondaryMap.remove(mediaServer.getId()); + offlineAblTimeMap.remove(mediaServer.getId()); + if (!mediaServer.isStatus()) { + logger.info("[ABL-连接成功] ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort()); + mediaServer.setStatus(true); + mediaServer.setHookAliveInterval(10F); + mediaServerService.update(mediaServer); + if(mediaServer.isAutoConfig()) { + if (config == null) { + ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServer); + JSONArray data = ablResult.getParams(); + if (data != null && !data.isEmpty()) { + config = AblServerConfig.getInstance(data); + } + } + if (config != null) { + initPort(mediaServer, config); + setAblConfig(mediaServer, false, config); + } + } + mediaServerService.update(mediaServer); + } + // 设置两次心跳未收到则认为zlm离线 + String key = "ABL-keepalive-" + mediaServer.getId(); + dynamicTask.startDelay(key, ()->{ + logger.warn("[ABL-心跳超时] ID:{}", mediaServer.getId()); + mediaServer.setStatus(false); + offlineABLPrimaryMap.put(mediaServer.getId(), mediaServer); + offlineAblTimeMap.put(mediaServer.getId(), System.currentTimeMillis()); + // TODO 发送离线通知 + mediaServerService.update(mediaServer); + }, (int)(mediaServer.getHookAliveInterval() * 2 * 1000)); + } + private void initPort(MediaServer mediaServer, AblServerConfig ablServerConfig) { + // 端口只会从配置中读取一次,一旦自己配置或者读取过了将不在配置 + if (ablServerConfig.getRtmpPort() != null && mediaServer.getRtmpPort() != ablServerConfig.getRtmpPort()) { + mediaServer.setRtmpPort(ablServerConfig.getRtmpPort()); + } + if (ablServerConfig.getRtspPort() != null && mediaServer.getRtspPort() != ablServerConfig.getRtspPort()) { + mediaServer.setRtspPort(ablServerConfig.getRtspPort()); + } + if (ablServerConfig.getHttpFlvPort() != null && mediaServer.getFlvPort() != ablServerConfig.getHttpFlvPort()) { + mediaServer.setFlvPort(ablServerConfig.getHttpFlvPort()); + } + if (ablServerConfig.getHttpMp4Port() != null && mediaServer.getMp4Port() != ablServerConfig.getHttpMp4Port()) { + mediaServer.setMp4Port(ablServerConfig.getHttpMp4Port()); + } + if (ablServerConfig.getWsFlvPort() != null && mediaServer.getWsFlvPort() != ablServerConfig.getWsFlvPort()) { + mediaServer.setWsFlvPort(ablServerConfig.getWsFlvPort()); + } + if (ablServerConfig.getPsTsRecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getPsTsRecvPort()) { + mediaServer.setRtpProxyPort(ablServerConfig.getPsTsRecvPort()); + } + if (ablServerConfig.getJtt1078RecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getJtt1078RecvPort()) { + mediaServer.setJttProxyPort(ablServerConfig.getJtt1078RecvPort()); + } + mediaServer.setHookAliveInterval(10F); + } + + public void setAblConfig(MediaServer mediaServerItem, boolean restart, AblServerConfig config) { + try { + if (config.getHookEnable() == 0) { + logger.info("[媒体服务节点-ABL] 开启HOOK功能 :{}", mediaServerItem.getId()); + ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, "hook_enable", "1"); + if (ablResult.getCode() == 0) { + logger.info("[媒体服务节点-ABL] 开启HOOK功能成功 :{}", mediaServerItem.getId()); + }else { + logger.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}->{}", mediaServerItem.getId(), ablResult.getMemo()); + } + } + }catch (Exception e) { + logger.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}", mediaServerItem.getId(), e); + } + // 设置相关的HOOK + String[] hookUrlArray = { + "on_stream_arrive", + "on_stream_none_reader", + "on_record_mp4", + "on_stream_disconnect", + "on_stream_not_found", + "on_server_started", + "on_publish", + "on_play", + "on_record_progress", + "on_server_keepalive", + "on_stream_not_arrive", + "on_delete_record_mp4", + }; + + String protocol = sslEnabled ? "https" : "http"; + String hookPrefix = String.format("%s://%s:%s/index/hook/abl", protocol, mediaServerItem.getHookIp(), serverPort); + Field[] fields = AblServerConfig.class.getDeclaredFields(); + for (Field field : fields) { + try { + if (field.isAnnotationPresent(ConfigKeyId.class)) { + ConfigKeyId configKeyId = field.getAnnotation(ConfigKeyId.class); + for (String hook : hookUrlArray) { + if (configKeyId.value().equals(hook)) { + String hookUrl = String.format("%s/%s", hookPrefix, hook); + field.setAccessible(true); + // 利用反射获取值后对比是否与配置中相同,不同则进行设置 + if (!hookUrl.equals(field.get(config))) { + ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, hook, hookUrl); + if (ablResult.getCode() == 0) { + logger.info("[媒体服务节点-ABL] 设置HOOK {} 成功 :{}", hook, mediaServerItem.getId()); + }else { + logger.info("[媒体服务节点-ABL] 设置HOOK {} 失败 :{}->{}", hook, mediaServerItem.getId(), ablResult.getMemo()); + } + } + } + } + } + }catch (Exception e) { + logger.info("[媒体服务节点-ABL] 设置HOOK 失败 :{}", mediaServerItem.getId(), e); + } + } + + + + +// Map param = new HashMap<>(); +// param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline +// if (mediaServerItem.getRtspPort() != 0) { +// param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); +// } +// param.put("hook.enable","1"); +// param.put("hook.on_flow_report",""); +// param.put("hook.on_play",String.format("%s/on_play", hookPrefix)); +// param.put("hook.on_http_access",""); +// param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix)); +// param.put("hook.on_record_ts",""); +// param.put("hook.on_rtsp_auth",""); +// param.put("hook.on_rtsp_realm",""); +// param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix)); +// param.put("hook.on_shell_login",""); +// param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix)); +// param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix)); +// param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix)); +// param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix)); +// param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix)); +// param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix)); +// param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix)); +// param.put("hook.timeoutSec","30"); +// param.put("hook.alive_interval", mediaServerItem.getHookAliveInterval()); +// // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 +// // 置0关闭此特性(推流断开会导致立即断开播放器) +// // 此参数不应大于播放器超时时间 +// // 优化此消息以更快的收到流注销事件 +// param.put("protocol.continue_push_ms", "3000" ); +// // 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流, +// // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 +// if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) { +// param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-")); +// } +// +// if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) { +// File recordPathFile = new File(mediaServerItem.getRecordPath()); +// param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); +// param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); +// param.put("record.appName", recordPathFile.getName()); +// } +// +// JSONObject responseJSON = ablResTfulUtils.setConfigParamValue(mediaServerItem, param); +// +// if (responseJSON != null && responseJSON.getInteger("code") == 0) { +// if (restart) { +// logger.info("[媒体服务节点] 设置成功,开始重启以保证配置生效 {} -> {}:{}", +// mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); +// ablResTfulUtils.restartServer(mediaServerItem); +// }else { +// logger.info("[媒体服务节点] 设置成功 {} -> {}:{}", +// mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); +// } +// }else { +// logger.info("[媒体服务节点] 设置媒体服务节点失败 {} -> {}:{}", +// mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); +// } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java new file mode 100644 index 000000000..9c019bd67 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java @@ -0,0 +1,540 @@ +package com.genersoft.iot.vmp.media.abl; + +import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.media.abl.bean.ABLResult; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Component +public class ABLRESTfulUtils { + + private final static Logger logger = LoggerFactory.getLogger(ABLRESTfulUtils.class); + + private OkHttpClient client; + + public interface RequestCallback{ + void run(String response); + } + public interface ResultCallback{ + void run(ABLResult response); + } + + private OkHttpClient getClient(){ + return getClient(null); + } + + private OkHttpClient getClient(Integer readTimeOut){ + if (client == null) { + if (readTimeOut == null) { + readTimeOut = 10; + } + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + //todo 暂时写死超时时间 均为5s + // 设置连接超时时间 + httpClientBuilder.connectTimeout(8,TimeUnit.SECONDS); + // 设置读取超时时间 + httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); + // 设置连接池 + httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); + client = httpClientBuilder.build(); + } + return client; + + } + + public String sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback) { + return sendPost(mediaServerItem, api, param, callback, null); + } + + + public String sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback, Integer readTimeOut) { + OkHttpClient client = getClient(readTimeOut); + + if (mediaServerItem == null) { + return null; + } + String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); + String result = null; + + FormBody.Builder builder = new FormBody.Builder(); + builder.add("secret",mediaServerItem.getSecret()); + if (param != null && !param.isEmpty()) { + for (String key : param.keySet()){ + if (param.get(key) != null) { + builder.add(key, param.get(key).toString()); + } + } + } + + FormBody body = builder.build(); + + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + if (callback == null) { + try { + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + result = responseBody.string(); + } + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + }catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ABL数据超时失败: %s, %s", url, e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ABL连接失败: %s, %s", url, e.getMessage())); + } + + }catch (Exception e){ + logger.error(String.format("访问ABL失败: %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(responseStr); + } catch (IOException e) { + logger.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) { + logger.error(String.format("连接ABL失败: %s, %s", call.request().toString(), e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ABL数据失败: %s, %s", call.request().toString(), e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ABL失败: %s, %s", call.request().toString(), e.getMessage())); + } + } + }); + } + return result; + } + + public String sendGet(MediaServer mediaServerItem, String api, Map param) { + OkHttpClient client = getClient(); + + if (mediaServerItem == null) { + return null; + } + String result = null; + StringBuilder stringBuffer = new StringBuilder(); + stringBuffer.append(String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api)); + if (param != null && !param.keySet().isEmpty()) { + stringBuffer.append("?secret=").append(mediaServerItem.getSecret()).append("&"); + int index = 1; + for (String key : param.keySet()){ + if (param.get(key) != null) { + stringBuffer.append(key + "=" + param.get(key)); + if (index < param.size()) { + stringBuffer.append("&"); + } + } + index++; + } + } + String url = stringBuffer.toString(); + logger.info("[访问ABL]: {}", url); + Request request = new Request.Builder() + .get() + .url(url) + .build(); + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + result = responseBody.string(); + } + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } catch (ConnectException e) { + logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + logger.info("请检查media配置并确认ABL已启动..."); + }catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + return result; + } + + public void sendGetForImg(MediaServer mediaServerItem, String api, Map params, String targetPath, String fileName) { + String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); + HttpUrl parseUrl = HttpUrl.parse(url); + if (parseUrl == null) { + return; + } + HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); + + httpBuilder.addQueryParameter("secret", mediaServerItem.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(); + logger.info(request.toString()); + try { + OkHttpClient client = getClient(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + if (targetPath != null) { + File snapFolder = new File(targetPath); + if (!snapFolder.exists()) { + if (!snapFolder.mkdirs()) { + logger.warn("{}路径创建失败", snapFolder.getAbsolutePath()); + } + } + File snapFile = new File(targetPath + File.separator + fileName); + FileOutputStream outStream = new FileOutputStream(snapFile); + + outStream.write(Objects.requireNonNull(response.body()).bytes()); + outStream.flush(); + outStream.close(); + } else { + logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + } + } else { + logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + } + Objects.requireNonNull(response.body()).close(); + } catch (ConnectException e) { + logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + logger.info("请检查media配置并确认ABL已启动..."); + } catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + } + + public void sendGetForImgForUrl(String url, String targetPath, String fileName) { + HttpUrl parseUrl = HttpUrl.parse(url); + if (parseUrl == null) { + return; + } + HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); + + Request request = new Request.Builder() + .url(httpBuilder.build()) + .build(); + logger.info(request.toString()); + try { + OkHttpClient client = getClient(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + if (targetPath != null) { + File snapFolder = new File(targetPath); + if (!snapFolder.exists()) { + if (!snapFolder.mkdirs()) { + logger.warn("{}路径创建失败", snapFolder.getAbsolutePath()); + } + } + File snapFile = new File(targetPath + File.separator + fileName); + FileOutputStream outStream = new FileOutputStream(snapFile); + + outStream.write(Objects.requireNonNull(response.body()).bytes()); + outStream.flush(); + outStream.close(); + } else { + logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + } + } else { + logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + } + Objects.requireNonNull(response.body()).close(); + } catch (ConnectException e) { + logger.error(String.format("连接ABL失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + logger.info("请检查media配置并确认ABL已启动..."); + } catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + } + + public Integer openRtpServer(MediaServer mediaServer, String app, String stream, int payload, Integer port, Integer tcpMode, Integer disableAudio, Boolean record, Boolean isJtt) { + Map param = new HashMap<>(); + param.put("vhost", "_defaultVhost_"); + param.put("app", app); + param.put("stream_id", stream); + param.put("payload", payload); + if (isJtt) { + // 1 PS 国标gb28181, 默认为1、 + // 2 ES 视频支持 H246\H265,音频只支持G711A、G711U 、AAC + // 3 XHB (一家公司的打包格式) 只支持视频,音频不能加入打包 + // 4 、Jt1078(2016版本)码流接入 + param.put("RtpPayloadDataType", 4); + param.put("jtt1078_version", "2019"); + } + if (port != null) { + param.put("port", port); + }else { + param.put("port", 0); + } + if (tcpMode != null) { + param.put("enable_tcp", tcpMode); + } + if (disableAudio != null) { + param.put("disableAudio", disableAudio); + } + if (record != null && record) { + param.put("enable_mp4", 1); + } + + String response = sendPost(mediaServer, "openRtpServer", param, null); + if (response == null) { + return 0; + }else { + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult.getCode() == 0) { + return ablResult.getPort(); + }else { + return 0; + } + } + } + + public ABLResult closeStreams(MediaServer mediaServerItem, String app, String stream) { + Map param = new HashMap<>(); + param.put("vhost", "__defaultVhost__"); + param.put("app", app); + param.put("stream", stream); + param.put("force", 1); + String response = sendPost(mediaServerItem, "close_streams", param, null); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult getServerConfig(MediaServer mediaServerItem){ + String response = sendPost(mediaServerItem, "getServerConfig", null, null); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult setConfigParamValue(MediaServer mediaServerItem, String key, Object value){ + Map param = new HashMap<>(); + param.put("key", key); + param.put("value", value); + String response = sendGet(mediaServerItem, "setConfigParamValue", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public void stopSendRtp(MediaServer mediaServer,String key) { + Map param = new HashMap<>(); + param.put("key", key); + sendPost(mediaServer,"stopSendRtp", param, null); + } + + public ABLResult getMediaList(MediaServer mediaServer, String app, String stream) { + Map param = new HashMap<>(); + param.put("app", app); + if (stream != null) { + param.put("stream", stream); + } + + String response = sendGet(mediaServer, "getMediaList", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult queryRecordList(MediaServer mediaServer, String app, String stream, String startTime, String endTime) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("starttime", startTime); + param.put("endtime", endTime); + String response = sendGet(mediaServer, "queryRecordList", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("timeout_sec", timeoutSec); + param.put("vhost", "_defaultVhost_"); +// JSONObject jsonObject = sendPost(mediaServer, "getSnap", param, null); +// if (jsonObject != null && jsonObject.getInteger("code") == 0) { +// String url = jsonObject.getString("url"); +// sendGetForImgForUrl(url, path, fileName); +// } + sendGetForImg(mediaServer, "getSnap", param, path, fileName); + + } + + public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { + try { + url = URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); + } + + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("url", url); + param.put("disableAudio", disableAudio? "1" : "0"); + param.put("enable_mp4", enableMp4 ? "1" : "0"); + // TODO rtpType timeout 尚不支持 + String response = sendGet(mediaServer, "addStreamProxy", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { + try { + url = URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); + } + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("url", url); + param.put("disableAudio", disableAudio); + param.put("enable_mp4", enableMp4); + // TODO rtpType timeout 尚不支持 + String response = sendGet(mediaServer, "addFFmpegProxy", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult delStreamProxy(MediaServer mediaServer, String streamKey) { + Map param = new HashMap<>(); + param.put("key", streamKey); + String response = sendGet(mediaServer, "delStreamProxy", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult delFFmpegProxy(MediaServer mediaServer, String streamKey) { + Map param = new HashMap<>(); + param.put("key", streamKey); + String response = sendGet(mediaServer, "delFFmpegProxy", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult pauseRtpServer(MediaServer mediaServer, String streamKey) { + Map param = new HashMap<>(); + param.put("key", streamKey); + String response = sendGet(mediaServer, "pauseRtpServer", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult resumeRtpServer(MediaServer mediaServer, String streamKey) { + Map param = new HashMap<>(); + param.put("key", streamKey); + String response = sendGet(mediaServer, "resumeRtpServer", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + + public ABLResult controlRecordPlay(MediaServer mediaServer, String app, String stream, String command, String value) { + Map param = new HashMap<>(); + param.put("app", app); + param.put("stream", stream); + param.put("command", command); + param.put("value", value); + String response = sendGet(mediaServer, "controlRecordPlay", param); + ABLResult ablResult = JSON.parseObject(response, ABLResult.class); + if (ablResult == null) { + return ABLResult.getFailForMediaServer(); + }else { + return ablResult; + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLMedia.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLMedia.java new file mode 100644 index 000000000..66532c6fc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLMedia.java @@ -0,0 +1,25 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import lombok.Data; + +@Data +public class ABLMedia { + private String key; + private String app; + private String stream; + private Integer sourceType; + private Long duration; + private String sim; + private Boolean status; + private Boolean enable_hls; + private Boolean transcodingStatus; + private String sourceURL; + private Integer networkType; + private Integer readerCount; + private String videoCodec; + private Integer width; + private Integer height; + private String audioCodec; + private Integer audioChannels; + private Integer audioSampleRate; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLRecordFile.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLRecordFile.java new file mode 100644 index 000000000..4af189fe6 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLRecordFile.java @@ -0,0 +1,10 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import lombok.Data; + +@Data +public class ABLRecordFile { + private String file; + private Long duration; + private ABLUrls url; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLResult.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLResult.java new file mode 100644 index 000000000..523e8b16e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLResult.java @@ -0,0 +1,49 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; + +import java.util.List; + +@Data +public class ABLResult { + private int code; + private String memo; + + + private String key; + private Integer port; + private JSONArray params; + private List mediaList; + + private String app; + private String stream; + private String starttime; + private String endtime; + private Long duration; + private ABLUrls url; + private List recordFileList; + + public static ABLResult getFailForMediaServer() { + ABLResult zlmResult = new ABLResult(); + zlmResult.setCode(-2); + zlmResult.setMemo("流媒体调用失败"); + return zlmResult; + } + + public static ABLResult getMediaServer(int code, String msg) { + ABLResult zlmResult = new ABLResult(); + zlmResult.setCode(code); + zlmResult.setMemo(msg); + return zlmResult; + } + @Override + public String toString() { + return "ZLMResult{" + + "code=" + code + + ", memo='" + memo + '\'' + + (key != null ? (", key=" + key) : "") + + (port != null ? (", port=" + port) : "") + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLUrls.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLUrls.java new file mode 100644 index 000000000..23a37c0e3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ABLUrls.java @@ -0,0 +1,21 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class ABLUrls { + private String rtsp; + private String rtmp; + + @JSONField(name = "http-flv") + private String httpFlv; + + @JSONField(name = "ws-flv") + private String wsFlv; + + @JSONField(name = "http-mp4") + private String httpMp4; + + private String download; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblServerConfig.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblServerConfig.java new file mode 100644 index 000000000..af36faf54 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblServerConfig.java @@ -0,0 +1,257 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +@Data +public class AblServerConfig { + + @ConfigKeyId("secret") + private String secret; + + @ConfigKeyId("ServerIP") + private String serverIp; + + @ConfigKeyId("mediaServerID") + private String mediaServerId; + + @ConfigKeyId("hook_enable") + private Integer hookEnable; + + @ConfigKeyId("enable_audio") + private Integer enableAudio; + + @ConfigKeyId("httpServerPort") + private Integer httpServerPort; + + @ConfigKeyId("rtspPort") + private Integer rtspPort; + + @ConfigKeyId("rtmpPort") + private Integer rtmpPort; + + @ConfigKeyId("httpFlvPort") + private Integer httpFlvPort; + + @ConfigKeyId("hls_enable") + private Integer hlsEnable; + + @ConfigKeyId("hlsPort") + private Integer hlsPort; + + @ConfigKeyId("wsFlvPort") + private Integer wsFlvPort; + + @ConfigKeyId("httpMp4Port") + private Integer httpMp4Port; + + @ConfigKeyId("ps_tsRecvPort") + private Integer psTsRecvPort; + + @ConfigKeyId("1078Port") + private Integer jtt1078RecvPort; + + @ConfigKeyId("hlsCutType") + private Integer hlsCutType; + + @ConfigKeyId("h265CutType") + private Integer h265CutType; + + @ConfigKeyId("RecvThreadCount") + private Integer RecvThreadCount; + + @ConfigKeyId("SendThreadCount") + private Integer SendThreadCount; + + @ConfigKeyId("GB28181RtpTCPHeadType") + private Integer GB28181RtpTCPHeadType; + + @ConfigKeyId("ReConnectingCount") + private Integer ReConnectingCount; + + @ConfigKeyId("maxTimeNoOneWatch") + private Integer maxTimeNoOneWatch; + + @ConfigKeyId("pushEnable_mp4") + private Integer pushEnableMp4; + + @ConfigKeyId("fileSecond") + private Integer fileSecond; + + @ConfigKeyId("fileKeepMaxTime") + private Integer fileKeepMaxTime; + + @ConfigKeyId("httpDownloadSpeed") + private Integer httpDownloadSpeed; + + @ConfigKeyId("RecordReplayThread") + private Integer RecordReplayThread; + + @ConfigKeyId("convertMaxObject") + private Integer convertMaxObject; + + @ConfigKeyId("version") + private String version; + + @ConfigKeyId("recordPath") + private String recordPath; + + @ConfigKeyId("picturePath") + private String picturePath; + + @ConfigKeyId("noneReaderDuration") + private Integer noneReaderDuration; + + @ConfigKeyId("on_server_started") + private String onServerStarted; + + @ConfigKeyId("on_server_keepalive") + private String onServerKeepalive; + + @ConfigKeyId("on_play") + private String onPlay; + + @ConfigKeyId("on_publish") + private String onPublish; + + @ConfigKeyId("on_stream_arrive") + private String onStreamArrive; + + @ConfigKeyId("on_stream_not_arrive") + private String onStreamNotArrive; + + @ConfigKeyId("on_stream_none_reader") + private String onStreamNoneReader; + + @ConfigKeyId("on_stream_disconnect") + private String onStreamDisconnect; + + @ConfigKeyId("on_stream_not_found") + private String onStreamNotFound; + + @ConfigKeyId("on_record_mp4") + private String onRecordMp4; + + @ConfigKeyId("on_delete_record_mp4") + private String onDeleteRecordMp4; + + @ConfigKeyId("on_record_progress") + private String onRecordProgress; + + @ConfigKeyId("on_record_ts") + private String onRecordTs; + + @ConfigKeyId("enable_GetFileDuration") + private Integer enableGetFileDuration; + + @ConfigKeyId("keepaliveDuration") + private Integer keepaliveDuration; + + @ConfigKeyId("captureReplayType") + private Integer captureReplayType; + + @ConfigKeyId("pictureMaxCount") + private Integer pictureMaxCount; + + @ConfigKeyId("videoFileFormat") + private Integer videoFileFormat; + + @ConfigKeyId("MaxDiconnectTimeoutSecond") + private Integer maxDiconnectTimeoutSecond; + + @ConfigKeyId("G711ConvertAAC") + private Integer g711ConvertAAC; + + @ConfigKeyId("filterVideo_enable") + private Integer filterVideoEnable; + + @ConfigKeyId("filterVideo_text") + private String filterVideoText; + + @ConfigKeyId("FilterFontSize") + private Integer filterFontSize; + + @ConfigKeyId("FilterFontColor") + private String filterFontColor; + + @ConfigKeyId("FilterFontLeft") + private Integer filterFontLeft; + + @ConfigKeyId("FilterFontTop") + private Integer filterFontTop; + + @ConfigKeyId("FilterFontAlpha") + private Double filterFontAlpha; + + @ConfigKeyId("convertOutWidth") + private Integer convertOutWidth; + + @ConfigKeyId("convertOutHeight") + private Integer convertOutHeight; + + @ConfigKeyId("convertOutBitrate") + private Integer convertOutBitrate; + + @ConfigKeyId("flvPlayAddMute") + private Integer flvPlayAddMute; + + @ConfigKeyId("gb28181LibraryUse") + private Integer gb28181LibraryUse; + + @ConfigKeyId("rtc.listening-ip") + private String rtcListeningIp; + + @ConfigKeyId("rtc.listening-port") + private Integer rtcListeningIpPort; + + @ConfigKeyId("rtc.external-ip") + private String rtcExternalIp; + + @ConfigKeyId("rtc.realm") + private String rtcRealm; + + @ConfigKeyId("rtc.user") + private String rtcUser; + + @ConfigKeyId("rtc.min-port") + private Integer rtcMinPort; + + @ConfigKeyId("rtc.max-port") + private Integer rtcMaxPort; + + public static AblServerConfig getInstance(JSONArray jsonArray) { + if (jsonArray == null || jsonArray.isEmpty()) { + return null; + } + AblServerConfig ablServerConfig = new AblServerConfig(); + Field[] fields = AblServerConfig.class.getDeclaredFields(); + Map fieldMap = new HashMap<>(); + for (Field field : fields) { + if (field.isAnnotationPresent(ConfigKeyId.class)) { + ConfigKeyId configKeyId = field.getAnnotation(ConfigKeyId.class); + fieldMap.put(configKeyId.value(), field); + } + } + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + if (jsonObject == null) { + continue; + } + for (String key : fieldMap.keySet()) { + if (jsonObject.containsKey(key)) { + Field field = fieldMap.get(key); + field.setAccessible(true); + try { + field.set(ablServerConfig, jsonObject.getObject(key, fieldMap.get(key).getType())); + } catch (IllegalAccessException e) {} + } + } + } + return ablServerConfig; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblUrls.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblUrls.java new file mode 100644 index 000000000..42e05a04d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/AblUrls.java @@ -0,0 +1,58 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +public class AblUrls { + private String rtsp; + private String rtmp; + private String httpFlv; + private String wsFlv; + private String httpMp4; + private String httpHls; + + public String getRtsp() { + return rtsp; + } + + public void setRtsp(String rtsp) { + this.rtsp = rtsp; + } + + public String getRtmp() { + return rtmp; + } + + public void setRtmp(String rtmp) { + this.rtmp = rtmp; + } + + public String getHttpFlv() { + return httpFlv; + } + + public void setHttpFlv(String httpFlv) { + this.httpFlv = httpFlv; + } + + public String getWsFlv() { + return wsFlv; + } + + public void setWsFlv(String wsFlv) { + this.wsFlv = wsFlv; + } + + public String getHttpMp4() { + return httpMp4; + } + + public void setHttpMp4(String httpMp4) { + this.httpMp4 = httpMp4; + } + + public String getHttpHls() { + return httpHls; + } + + public void setHttpHls(String httpHls) { + this.httpHls = httpHls; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ConfigKeyId.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ConfigKeyId.java new file mode 100644 index 000000000..244bb9495 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/ConfigKeyId.java @@ -0,0 +1,10 @@ +package com.genersoft.iot.vmp.media.abl.bean; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConfigKeyId { + String value(); +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/ABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/ABLHookParam.java new file mode 100644 index 000000000..796935ea8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/ABLHookParam.java @@ -0,0 +1,65 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +public class ABLHookParam { + private String mediaServerId; + + /** + * 应用名 + */ + private String app; + + /** + * 流id + */ + private String stream; + + /** + * 媒体流来源编号,可以根据这个key进行关闭流媒体 可以调用delMediaStream或close_streams 函数进行关闭 + */ + private String key; + + /** + * 媒体流来源网络编号,可参考附表 + */ + private Integer networkType; + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getStream() { + return stream; + } + + public void setStream(String stream) { + this.stream = stream; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Integer getNetworkType() { + return networkType; + } + + public void setNetworkType(Integer networkType) { + this.networkType = networkType; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPlayABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPlayABLHookParam.java new file mode 100644 index 000000000..77c180d36 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPlayABLHookParam.java @@ -0,0 +1,23 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class OnPlayABLHookParam extends ABLHookParam{ + + private String ip; + private Integer port; + private String params; + + @Override + public String toString() { + return "OnPlayABLHookParam{" + + "ip='" + ip + '\'' + + ", port=" + port + + ", params='" + params + '\'' + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPublishABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPublishABLHookParam.java new file mode 100644 index 000000000..11da2740b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnPublishABLHookParam.java @@ -0,0 +1,31 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +public class OnPublishABLHookParam extends ABLHookParam{ + private String ip; + private Integer port; + private String params; + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getParams() { + return params; + } + + public void setParams(String params) { + this.params = params; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordMp4ABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordMp4ABLHookParam.java new file mode 100644 index 000000000..6aaddfed2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordMp4ABLHookParam.java @@ -0,0 +1,13 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OnRecordMp4ABLHookParam extends ABLHookParam{ + private String fileName; + private String startTime; + private String endTime; + private long fileSize; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordProgressABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordProgressABLHookParam.java new file mode 100644 index 000000000..93e6c0974 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnRecordProgressABLHookParam.java @@ -0,0 +1,40 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +public class OnRecordProgressABLHookParam extends OnRecordMp4ABLHookParam{ + private Integer currentFileDuration; + private Integer TotalVideoDuration; + private String startTime; + private String endTime; + + public Integer getCurrentFileDuration() { + return currentFileDuration; + } + + public void setCurrentFileDuration(Integer currentFileDuration) { + this.currentFileDuration = currentFileDuration; + } + + public Integer getTotalVideoDuration() { + return TotalVideoDuration; + } + + public void setTotalVideoDuration(Integer totalVideoDuration) { + TotalVideoDuration = totalVideoDuration; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerKeepaliveABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerKeepaliveABLHookParam.java new file mode 100644 index 000000000..ea1ac9702 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerKeepaliveABLHookParam.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +public class OnServerKeepaliveABLHookParam { + private String localipAddress; + private String mediaServerId; + private String datetime; + + + public String getLocalipAddress() { + return localipAddress; + } + + public void setLocalipAddress(String localipAddress) { + this.localipAddress = localipAddress; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getDatetime() { + return datetime; + } + + public void setDatetime(String datetime) { + this.datetime = datetime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerStaredABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerStaredABLHookParam.java new file mode 100644 index 000000000..a9ec44c0c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnServerStaredABLHookParam.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +public class OnServerStaredABLHookParam { + private String localipAddress; + private String mediaServerId; + private String datetime; + + + public String getLocalipAddress() { + return localipAddress; + } + + public void setLocalipAddress(String localipAddress) { + this.localipAddress = localipAddress; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getDatetime() { + return datetime; + } + + public void setDatetime(String datetime) { + this.datetime = datetime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnStreamArriveABLHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnStreamArriveABLHookParam.java new file mode 100644 index 000000000..0f552d1c2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/bean/hook/OnStreamArriveABLHookParam.java @@ -0,0 +1,112 @@ +package com.genersoft.iot.vmp.media.abl.bean.hook; + +import com.genersoft.iot.vmp.media.abl.bean.AblUrls; +import lombok.Getter; +import lombok.Setter; + +/** + * 流到来的事件 + */ +@Getter +@Setter +public class OnStreamArriveABLHookParam extends ABLHookParam{ + + + + /** + * 推流鉴权Id + */ + private String callId; + + /** + * 状态 + */ + private Boolean status; + + + /** + * + */ + private Boolean enableHls; + + + /** + * + */ + private Boolean transcodingStatus; + + + /** + * + */ + private String sourceURL; + + + /** + * + */ + private Integer readerCount; + + + /** + * + */ + private Integer noneReaderDuration; + + + /** + * + */ + private String videoCodec; + + + /** + * + */ + private Integer videoFrameSpeed; + + + /** + * + */ + private Integer width; + + + /** + * + */ + private Integer height; + + + /** + * + */ + private Integer videoBitrate; + + + /** + * + */ + private String audioCodec; + + + /** + * + */ + private Integer audioChannels; + + + /** + * + */ + private Integer audioSampleRate; + + + /** + * + */ + private Integer audioBitrate; + + + private AblUrls url; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerKeepaliveEvent.java b/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerKeepaliveEvent.java new file mode 100644 index 000000000..74465e4c1 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerKeepaliveEvent.java @@ -0,0 +1,24 @@ +package com.genersoft.iot.vmp.media.abl.event; + +import com.genersoft.iot.vmp.media.bean.MediaServer; +import org.springframework.context.ApplicationEvent; + +/** + * zlm 心跳事件 + */ +public class HookAblServerKeepaliveEvent extends ApplicationEvent { + + public HookAblServerKeepaliveEvent(Object source) { + super(source); + } + + private MediaServer mediaServerItem; + + public MediaServer getMediaServerItem() { + return mediaServerItem; + } + + public void setMediaServerItem(MediaServer mediaServerItem) { + this.mediaServerItem = mediaServerItem; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerStartEvent.java b/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerStartEvent.java new file mode 100644 index 000000000..12bdac06f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/event/HookAblServerStartEvent.java @@ -0,0 +1,24 @@ +package com.genersoft.iot.vmp.media.abl.event; + +import com.genersoft.iot.vmp.media.bean.MediaServer; +import org.springframework.context.ApplicationEvent; + +/** + * zlm server_start事件 + */ +public class HookAblServerStartEvent extends ApplicationEvent { + + public HookAblServerStartEvent(Object source) { + super(source); + } + + private MediaServer mediaServerItem; + + public MediaServer getMediaServerItem() { + return mediaServerItem; + } + + public void setMediaServerItem(MediaServer mediaServerItem) { + this.mediaServerItem = mediaServerItem; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java index e8caaccca..54cf6ebcd 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java @@ -2,7 +2,10 @@ package com.genersoft.iot.vmp.media.bean; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.media.abl.bean.ABLMedia; +import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; import com.genersoft.iot.vmp.utils.MediaServerUtils; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -231,4 +234,75 @@ public class MediaInfo { } return mediaInfo; } + + public static MediaInfo getInstance(OnStreamArriveABLHookParam param, MediaServer mediaServer) { + + MediaInfo mediaInfo = new MediaInfo(); + mediaInfo.setApp(param.getApp()); + mediaInfo.setStream(param.getStream()); + mediaInfo.setMediaServer(mediaServer); + mediaInfo.setReaderCount(param.getReaderCount()); + mediaInfo.setOnline(true); + mediaInfo.setVideoCodec(param.getVideoCodec()); + switch (param.getNetworkType()) { + case 21: + mediaInfo.setOriginType(OriginType.RTMP_PUSH.ordinal()); + break; + case 23: + mediaInfo.setOriginType(OriginType.RTSP_PUSH.ordinal()); + break; + case 30: + case 31: + case 32: + case 33: + mediaInfo.setOriginType(OriginType.PULL.ordinal()); + break; + default: + mediaInfo.setOriginType(OriginType.UNKNOWN.ordinal()); + break; + + } + mediaInfo.setWidth(param.getWidth()); + mediaInfo.setHeight(param.getHeight()); + mediaInfo.setAudioCodec(param.getAudioCodec()); + mediaInfo.setAudioChannels(param.getAudioChannels()); + mediaInfo.setAudioSampleRate(param.getAudioSampleRate()); + + return mediaInfo; + } + + public static MediaInfo getInstance(ABLMedia ablMedia, MediaServer mediaServer) { + MediaInfo mediaInfo = new MediaInfo(); + mediaInfo.setApp(ablMedia.getApp()); + mediaInfo.setStream(ablMedia.getStream()); + mediaInfo.setMediaServer(mediaServer); + mediaInfo.setReaderCount(ablMedia.getReaderCount()); + mediaInfo.setOnline(true); + mediaInfo.setVideoCodec(ablMedia.getVideoCodec()); + switch (ablMedia.getNetworkType()) { + case 21: + mediaInfo.setOriginType(OriginType.RTMP_PUSH.ordinal()); + break; + case 23: + mediaInfo.setOriginType(OriginType.RTSP_PUSH.ordinal()); + break; + case 30: + case 31: + case 32: + case 33: + mediaInfo.setOriginType(OriginType.PULL.ordinal()); + break; + default: + mediaInfo.setOriginType(OriginType.UNKNOWN.ordinal()); + break; + + } + mediaInfo.setWidth(ablMedia.getWidth()); + mediaInfo.setHeight(ablMedia.getHeight()); + mediaInfo.setAudioCodec(ablMedia.getAudioCodec()); + mediaInfo.setAudioChannels(ablMedia.getAudioChannels()); + mediaInfo.setAudioSampleRate(ablMedia.getAudioSampleRate()); + + return mediaInfo; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java index 431eb29d8..38de6394a 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java +++ b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.media.bean; +import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -40,6 +41,9 @@ public class MediaServer { @Schema(description = "https-flv端口") private int flvSSLPort; + @Schema(description = "mp4端口") + private int mp4Port; + @Schema(description = "ws-flv端口") private int wsFlvPort; @@ -52,6 +56,9 @@ public class MediaServer { @Schema(description = "RTP收流端口(单端口模式有用)") private int rtpProxyPort; + @Schema(description = "1078收流端口(单端口模式有用)") + private int jttProxyPort; + @Schema(description = "RTSP端口") private int rtspPort; @@ -118,11 +125,7 @@ public class MediaServer { sdpIp = ObjectUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp(); streamIp = ObjectUtils.isEmpty(zlmServerConfig.getStreamIp())? zlmServerConfig.getIp(): zlmServerConfig.getStreamIp(); httpPort = zlmServerConfig.getHttpPort(); - flvPort = zlmServerConfig.getHttpPort(); - wsFlvPort = zlmServerConfig.getHttpPort(); httpSSlPort = zlmServerConfig.getHttpSSLport(); - flvSSLPort = zlmServerConfig.getHttpSSLport(); - wsFlvSSLPort = zlmServerConfig.getHttpSSLport(); rtmpPort = zlmServerConfig.getRtmpPort(); rtmpSSlPort = zlmServerConfig.getRtmpSslPort(); rtpProxyPort = zlmServerConfig.getRtpProxyPort(); @@ -137,4 +140,24 @@ public class MediaServer { transcodeSuffix = zlmServerConfig.getTranscodeSuffix(); } + + public MediaServer(AblServerConfig config, String sipIp) { + id = config.getMediaServerId(); + ip = config.getServerIp(); + hookIp = sipIp; + sdpIp = config.getServerIp(); + streamIp = config.getServerIp(); + httpPort = config.getHttpServerPort(); + flvPort = config.getHttpFlvPort(); + mp4Port = config.getHttpMp4Port(); + wsFlvPort = config.getWsFlvPort(); + rtmpPort = config.getRtmpPort(); + rtpProxyPort = config.getJtt1078RecvPort(); + rtspPort = config.getRtspPort(); + autoConfig = true; // 默认值true; + secret = config.getSecret(); + rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口 + rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号 + recordAssistPort = 0; // 默认关闭 + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/bean/RecordInfo.java b/src/main/java/com/genersoft/iot/vmp/media/bean/RecordInfo.java index 35b023f78..b90305854 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/bean/RecordInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/media/bean/RecordInfo.java @@ -1,28 +1,64 @@ package com.genersoft.iot.vmp.media.bean; +import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.utils.DateUtil; import lombok.Data; @Data public class RecordInfo { + private String app; + private String stream; private String fileName; private String filePath; private long fileSize; private String folder; private String url; + /** + * 单位毫秒 + */ private long startTime; + /** + * 单位毫秒 + */ private double timeLen; private String params; public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) { RecordInfo recordInfo = new RecordInfo(); + recordInfo.setApp(hookParam.getApp()); + recordInfo.setStream(hookParam.getStream()); recordInfo.setFileName(hookParam.getFile_name()); recordInfo.setUrl(hookParam.getUrl()); recordInfo.setFolder(hookParam.getFolder()); recordInfo.setFilePath(hookParam.getFile_path()); recordInfo.setFileSize(hookParam.getFile_size()); - recordInfo.setStartTime(hookParam.getStart_time()); - recordInfo.setTimeLen(hookParam.getTime_len()); + recordInfo.setStartTime(hookParam.getStart_time() * 1000); + recordInfo.setTimeLen(hookParam.getTime_len() * 1000); + return recordInfo; + } + + public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) { + RecordInfo recordInfo = new RecordInfo(); + recordInfo.setApp(hookParam.getApp()); + recordInfo.setStream(hookParam.getStream()); + recordInfo.setFileName(hookParam.getFileName()); + recordInfo.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime())); + recordInfo.setTimeLen(DateUtil.urlToTimestampMs(hookParam.getEndTime()) - recordInfo.getStartTime()); + recordInfo.setFileSize(hookParam.getFileSize()); + return recordInfo; + } + + public static RecordInfo getInstance(CloudRecordItem cloudRecordItem) { + RecordInfo recordInfo = new RecordInfo(); + recordInfo.setApp(cloudRecordItem.getApp()); + recordInfo.setStream(cloudRecordItem.getStream()); + recordInfo.setFileName(cloudRecordItem.getFileName()); + recordInfo.setStartTime(cloudRecordItem.getStartTime()); + recordInfo.setTimeLen(cloudRecordItem.getTimeLen()); + recordInfo.setFileSize(cloudRecordItem.getFileSize()); + recordInfo.setFilePath(cloudRecordItem.getFilePath()); return recordInfo; } diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java index b91b77b0a..f26f50b51 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.media.event.media; +import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; @@ -29,6 +30,15 @@ public class MediaArrivalEvent extends MediaEvent { mediaArrivalEvent.setParamMap(hookParam.getParamMap()); return mediaArrivalEvent; } + public static MediaArrivalEvent getInstance(Object source, OnStreamArriveABLHookParam hookParam, MediaServer mediaServer){ + MediaArrivalEvent mediaArrivalEvent = new MediaArrivalEvent(source); + mediaArrivalEvent.setMediaInfo(MediaInfo.getInstance(hookParam, mediaServer)); + mediaArrivalEvent.setApp(hookParam.getApp()); + mediaArrivalEvent.setStream(hookParam.getStream()); + mediaArrivalEvent.setMediaServer(mediaServer); + mediaArrivalEvent.setCallId(hookParam.getCallId()); + return mediaArrivalEvent; + } @Getter @Setter diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaDepartureEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaDepartureEvent.java index edd945ad4..02b99f31c 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaDepartureEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaDepartureEvent.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.media.event.media; +import com.genersoft.iot.vmp.media.abl.bean.hook.ABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; @@ -19,4 +20,12 @@ public class MediaDepartureEvent extends MediaEvent { mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } + + public static MediaDepartureEvent getInstance(Object source, ABLHookParam hookParam, MediaServer mediaServer){ + MediaDepartureEvent mediaDepartureEven = new MediaDepartureEvent(source); + mediaDepartureEven.setApp(hookParam.getApp()); + mediaDepartureEven.setStream(hookParam.getStream()); + mediaDepartureEven.setMediaServer(mediaServer); + return mediaDepartureEven; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaEvent.java index b6fb89022..1ddcdd459 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaEvent.java @@ -20,6 +20,16 @@ public class MediaEvent extends ApplicationEvent { private String schema; + private String params; + + public String getParams() { + return params; + } + + public void setParams(String params) { + this.params = params; + } + public String getApp() { return app; diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaNotFoundEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaNotFoundEvent.java index 2415566a9..d4152ee79 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaNotFoundEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaNotFoundEvent.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.media.event.media; +import com.genersoft.iot.vmp.media.abl.bean.hook.ABLHookParam; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamNotFoundHookParam; @@ -17,6 +18,15 @@ public class MediaNotFoundEvent extends MediaEvent { mediaDepartureEven.setStream(hookParam.getStream()); mediaDepartureEven.setSchema(hookParam.getSchema()); mediaDepartureEven.setMediaServer(mediaServer); + mediaDepartureEven.setParams(hookParam.getParams()); + return mediaDepartureEven; + } + + public static MediaNotFoundEvent getInstance(Object source, ABLHookParam hookParam, MediaServer mediaServer){ + MediaNotFoundEvent mediaDepartureEven = new MediaNotFoundEvent(source); + mediaDepartureEven.setApp(hookParam.getApp()); + mediaDepartureEven.setStream(hookParam.getStream()); + mediaDepartureEven.setMediaServer(mediaServer); return mediaDepartureEven; } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaPublishEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaPublishEvent.java index 0d9f032c7..4b5fa7052 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaPublishEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaPublishEvent.java @@ -21,13 +21,4 @@ public class MediaPublishEvent extends MediaEvent { return mediaPublishEvent; } - private String params; - - public String getParams() { - return params; - } - - public void setParams(String params) { - this.params = params; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordMp4Event.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordMp4Event.java index 093c3c255..9fd6defc8 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordMp4Event.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordMp4Event.java @@ -1,5 +1,7 @@ package com.genersoft.iot.vmp.media.event.media; +import com.genersoft.iot.vmp.media.abl.ABLHttpHookListener; +import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam; import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; @@ -24,6 +26,16 @@ public class MediaRecordMp4Event extends MediaEvent { return mediaRecordMp4Event; } + public static MediaRecordMp4Event getInstance(ABLHttpHookListener source, OnRecordMp4ABLHookParam hookParam, MediaServer mediaServer) { + MediaRecordMp4Event mediaRecordMp4Event = new MediaRecordMp4Event(source); + mediaRecordMp4Event.setApp(hookParam.getApp()); + mediaRecordMp4Event.setStream(hookParam.getStream()); + RecordInfo recordInfo = RecordInfo.getInstance(hookParam); + mediaRecordMp4Event.setRecordInfo(recordInfo); + mediaRecordMp4Event.setMediaServer(mediaServer); + return mediaRecordMp4Event; + } + public RecordInfo getRecordInfo() { return recordInfo; } diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordProcessEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordProcessEvent.java new file mode 100644 index 000000000..e4d1a5647 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaRecordProcessEvent.java @@ -0,0 +1,77 @@ +package com.genersoft.iot.vmp.media.event.media; + +import com.genersoft.iot.vmp.media.abl.ABLHttpHookListener; +import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordProgressABLHookParam; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.utils.DateUtil; + +import java.util.Date; + +/** + * 录像文件进度通知事件 + */ +public class MediaRecordProcessEvent extends MediaEvent { + + private Integer currentFileDuration; + private Integer TotalVideoDuration; + private String fileName; + private long startTime; + private long endTime; + + public MediaRecordProcessEvent(Object source) { + super(source); + } + + public static MediaRecordProcessEvent getInstance(ABLHttpHookListener source, OnRecordProgressABLHookParam hookParam, MediaServer mediaServer) { + MediaRecordProcessEvent mediaRecordMp4Event = new MediaRecordProcessEvent(source); + mediaRecordMp4Event.setApp(hookParam.getApp()); + mediaRecordMp4Event.setStream(hookParam.getStream()); + mediaRecordMp4Event.setCurrentFileDuration(hookParam.getCurrentFileDuration()); + mediaRecordMp4Event.setTotalVideoDuration(hookParam.getTotalVideoDuration()); + mediaRecordMp4Event.setMediaServer(mediaServer); + mediaRecordMp4Event.setFileName(hookParam.getFileName()); + mediaRecordMp4Event.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime())); + mediaRecordMp4Event.setEndTime(DateUtil.urlToTimestampMs(hookParam.getEndTime())); + return mediaRecordMp4Event; + } + + public Integer getCurrentFileDuration() { + return currentFileDuration; + } + + public void setCurrentFileDuration(Integer currentFileDuration) { + this.currentFileDuration = currentFileDuration; + } + + public Integer getTotalVideoDuration() { + return TotalVideoDuration; + } + + public void setTotalVideoDuration(Integer totalVideoDuration) { + TotalVideoDuration = totalVideoDuration; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java index e3256d18e..b0a768c84 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java @@ -5,6 +5,9 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.bean.RecordInfo; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; @@ -14,10 +17,14 @@ import java.util.Map; public interface IMediaNodeServerService { int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode); - void closeRtpServer(MediaServer mediaServer, String streamId); - void closeRtpServer(MediaServer mediaServer, String streamId, CommonCallback callback); + + int createJTTServer(MediaServer mediaServer, String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode); + + void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback); + + void closeStreams(MediaServer mediaServer, String app, String stream); Boolean updateRtpServerSSRC(MediaServer mediaServer, String stream, String ssrc); @@ -38,7 +45,7 @@ public interface IMediaNodeServerService { Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String stream); - void getSnap(MediaServer mediaServer, String streamUrl, int timeoutSec, int expireSec, String path, String fileName); + void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream); @@ -48,8 +55,6 @@ public interface IMediaNodeServerService { String getFfmpegCmd(MediaServer mediaServer, String cmdKey); - WVPResult addFFmpegSource(MediaServer mediaServer, String srcUrl, String dstUrl, int timeoutMs, boolean enableAudio, boolean enableMp4, String ffmpegCmdKey); - WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout); Boolean delFFmpegSource(MediaServer mediaServer, String streamKey); @@ -62,17 +67,25 @@ public interface IMediaNodeServerService { void startSendRtpStream(MediaServer mediaServer, SendRtpInfo sendRtpItem); + Integer startSendRtpTalk(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout); + Long updateDownloadProcess(MediaServer mediaServer, String app, String stream); - StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy); + String startProxy(MediaServer mediaServer, StreamProxy streamProxy); - void stopProxy(MediaServer mediaServer, String streamKey); + void stopProxy(MediaServer mediaServer, String streamKey, String type); List listRtpServer(MediaServer mediaServer); - void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath); + void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback callback); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); + + DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo); + + StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay); + + void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java index 4eda22704..424603021 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java @@ -5,8 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; -import com.genersoft.iot.vmp.service.bean.MediaServerLoad; -import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.media.bean.RecordInfo; +import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; @@ -41,6 +41,10 @@ public interface IMediaServerService { void closeRTPServer(MediaServer mediaServerItem, String streamId, CommonCallback callback); + SSRCInfo openJTTServer(MediaServer mediaServerItem, String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode); + + void closeJTTServer(MediaServer mediaServerItem, String streamId, CommonCallback callback); + Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String streamId, String ssrc); void closeRTPServer(String mediaServerId, String streamId); @@ -85,7 +89,7 @@ public interface IMediaServerService { Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String stream); - void getSnap(MediaServer mediaServerItemInuse, String streamUrl, int timeoutSec, int expireSec, String path, String fileName); + void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServerItem, String app, String stream); @@ -97,8 +101,6 @@ public interface IMediaServerService { void closeStreams(MediaServer mediaServerItem, String app, String stream); - WVPResult addFFmpegSource(MediaServer mediaServerItem, String srcUrl, String dstUrl, int timeoutMs, boolean enableAudio, boolean enableMp4, String ffmpegCmdKey); - WVPResult addStreamProxy(MediaServer mediaServerItem, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout); Boolean delFFmpegSource(MediaServer mediaServerItem, String streamKey); @@ -144,15 +146,17 @@ public interface IMediaServerService { Integer startSendRtpPassive(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout); + Integer startSendRtpTalk(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout); + void startSendRtp(MediaServer mediaServer, SendRtpInfo sendRtpItem); MediaServer getMediaServerByAppAndStream(String app, String stream); Long updateDownloadProcess(MediaServer mediaServerItem, String app, String stream); - StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy); + String startProxy(MediaServer mediaServer, StreamProxy streamProxy); - void stopProxy(MediaServer mediaServer, String streamKey); + void stopProxy(MediaServer mediaServer, String streamKey, String type); StreamInfo getMediaByAppAndStream(String app, String stream); @@ -160,9 +164,13 @@ public interface IMediaServerService { List listRtpServer(MediaServer mediaServer); - StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath); + void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback callback); void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema); void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema); + + DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo); + + void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java index 8d4ef0a5c..6d508658c 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java @@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; @@ -20,8 +21,7 @@ import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType; -import com.genersoft.iot.vmp.service.bean.MediaServerLoad; -import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.MediaServerMapper; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; @@ -41,6 +41,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.*; @@ -214,6 +215,27 @@ public class MediaServerServiceImpl implements IMediaServerService { return rtpServerPort; } + @Override + public SSRCInfo openJTTServer(MediaServer mediaServer, @NotNull String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode) { + if (mediaServer == null || mediaServer.getId() == null) { + log.info("[openJTTServer] 失败, mediaServer == null || mediaServer.getId() == null"); + return null; + } + + int rtpServerPort; + if (mediaServer.isRtpEnable()) { + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[openJTTServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + return null; + } + rtpServerPort = mediaNodeServerService.createJTTServer(mediaServer, streamId, port, disableVideo, disableAudio, tcpMode); + } else { + rtpServerPort = mediaServer.getJttProxyPort(); + } + return new SSRCInfo(rtpServerPort, null, "1078", streamId, null); + } + @Override public List listRtpServer(MediaServer mediaServer) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); @@ -234,7 +256,7 @@ public class MediaServerServiceImpl implements IMediaServerService { log.info("[closeRTPServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } - mediaNodeServerService.closeRtpServer(mediaServer, streamId); + mediaNodeServerService.closeRtpServer(mediaServer, streamId, null); } @Override @@ -268,6 +290,20 @@ public class MediaServerServiceImpl implements IMediaServerService { mediaNodeServerService.closeStreams(mediaServer, "rtp", streamId); } + @Override + public void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback) { + if (mediaServer == null) { + callback.run(false); + return; + } + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[closeJTTServer] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + return; + } + mediaNodeServerService.closeJTTServer(mediaServer, streamId, callback); + } + @Override public Boolean updateRtpServerSSRC(MediaServer mediaServer, String streamId, String ssrc) { if (mediaServer == null) { @@ -665,13 +701,13 @@ public class MediaServerServiceImpl implements IMediaServerService { } @Override - public void getSnap(MediaServer mediaServer, String streamUrl, int timeoutSec, int expireSec, String path, String fileName) { + public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); return; } - mediaNodeServerService.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName); + mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName); } @Override @@ -724,16 +760,6 @@ public class MediaServerServiceImpl implements IMediaServerService { mediaNodeServerService.closeStreams(mediaServer, app, stream); } - @Override - public WVPResult addFFmpegSource(MediaServer mediaServer, String srcUrl, String dstUrl, int timeoutMs, boolean enableAudio, boolean enableMp4, String ffmpegCmdKey) { - IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); - if (mediaNodeServerService == null) { - log.info("[addFFmpegSource] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); - return WVPResult.fail(ErrorCode.ERROR400); - } - return mediaNodeServerService.addFFmpegSource(mediaServer, srcUrl, dstUrl, timeoutMs, enableAudio, enableMp4, ffmpegCmdKey); - } - @Override public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { @@ -815,68 +841,12 @@ public class MediaServerServiceImpl implements IMediaServerService { @Override public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) { - StreamInfo streamInfoResult = new StreamInfo(); - streamInfoResult.setStream(stream); - streamInfoResult.setApp(app); - if (addr == null) { - addr = mediaServer.getStreamIp(); + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[getStreamInfoByAppAndStream] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + return null; } - - streamInfoResult.setIp(addr); - if (mediaInfo != null) { - streamInfoResult.setServerId(mediaInfo.getServerId()); - }else { - streamInfoResult.setServerId(userSetting.getServerId()); - } - - streamInfoResult.setMediaServer(mediaServer); - Map param = new HashMap<>(); - if (!ObjectUtils.isEmpty(callId)) { - param.put("callId", callId); - } - if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { - param.put("originTypeStr", mediaInfo.getOriginTypeStr()); - } - StringBuilder callIdParamBuilder = new StringBuilder(); - if (!param.isEmpty()) { - callIdParamBuilder.append("?"); - for (Map.Entry entry : param.entrySet()) { - callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); - callIdParamBuilder.append("&"); - } - callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); - } - - String callIdParam = callIdParamBuilder.toString(); - - streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); - streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); - - - if ("abl".equals(mediaServer.getType())) { - String flvFile = String.format("%s/%s.flv%s", app, stream, callIdParam); - streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),mediaServer.getFlvSSLPort(), flvFile); - streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),mediaServer.getWsFlvSSLPort(), flvFile); - }else { - String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam); - streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),mediaServer.getFlvSSLPort(), flvFile); - streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),mediaServer.getWsFlvSSLPort(), flvFile); - } - - streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setHls(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); - - streamInfoResult.setMediaInfo(mediaInfo); - - if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { - String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); - mediaServer.setTranscodeSuffix(null); - StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); - streamInfoResult.setTranscodeStream(transcodeStreamInfo); - } - return streamInfoResult; + return mediaNodeServerService.getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, addr, callId, isPlay); } @Override @@ -900,6 +870,16 @@ public class MediaServerServiceImpl implements IMediaServerService { return mediaNodeServerService.startSendRtpPassive(mediaServer, sendRtpItem, timeout); } + @Override + public Integer startSendRtpTalk(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[startSendRtpPassive] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); + } + return mediaNodeServerService.startSendRtpTalk(mediaServer, sendRtpItem, timeout); + } + @Override public void startSendRtp(MediaServer mediaServer, SendRtpInfo sendRtpItem) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); @@ -952,7 +932,7 @@ public class MediaServerServiceImpl implements IMediaServerService { } @Override - public StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy) { + public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[startProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); @@ -962,24 +942,34 @@ public class MediaServerServiceImpl implements IMediaServerService { } @Override - public void stopProxy(MediaServer mediaServer, String streamKey) { + public void stopProxy(MediaServer mediaServer, String streamKey, String type) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[stopProxy] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } - mediaNodeServerService.stopProxy(mediaServer, streamKey); + mediaNodeServerService.stopProxy(mediaServer, streamKey, type); } @Override - public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { + public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[loadMP4FileForDate] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); + } + mediaNodeServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback); + + } + + @Override + public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[loadMP4File] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); } - mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath); - return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null); + mediaNodeServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, callback); } @Override @@ -1001,4 +991,14 @@ public class MediaServerServiceImpl implements IMediaServerService { } mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema); } + + @Override + public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) { + IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); + if (mediaNodeServerService == null) { + log.info("[setRecordSpeed] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类"); + } + return mediaNodeServerService.getDownloadFilePath(mediaServer, recordInfo); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java index 6a2b7d247..23b8b374e 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java @@ -209,7 +209,7 @@ public class AssistRESTfulUtils { if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); - callback.run(JSON.parseObject(responseStr)); + callback.run(responseStr); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java index 6786404aa..a6c53f517 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java @@ -10,12 +10,19 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.bean.RecordInfo; +import com.genersoft.iot.vmp.media.event.hook.Hook; +import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; +import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; -import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; +import com.genersoft.iot.vmp.media.zlm.dto.*; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; @@ -36,14 +43,12 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { @Autowired private UserSetting userSetting; - @Override - public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { - return zlmServerFactory.createRTPServer(mediaServer, streamId, ssrc, port, onlyAuto, reUsePort, tcpMode); - } + @Autowired + private HookSubscribe subscribe; @Override - public void closeRtpServer(MediaServer mediaServer, String streamId) { - zlmServerFactory.closeRtpServer(mediaServer, streamId); + public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { + return zlmServerFactory.createRTPServer(mediaServer, "rtp", streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode); } @Override @@ -51,6 +56,16 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { zlmServerFactory.closeRtpServer(mediaServer, streamId, callback); } + @Override + public int createJTTServer(MediaServer mediaServer, String streamId, Integer port, Boolean disableVideo, Boolean disableAudio, Integer tcpMode) { + return zlmServerFactory.createRTPServer(mediaServer, "1078", streamId, 0, port, disableVideo, disableAudio, false, tcpMode); + } + + @Override + public void closeJTTServer(MediaServer mediaServer, String streamId, CommonCallback callback) { + zlmServerFactory.closeRtpServer(mediaServer, streamId, callback); + } + @Override public void closeStreams(MediaServer mediaServer, String app, String stream) { zlmresTfulUtils.closeStreams(mediaServer, app, stream); @@ -66,9 +81,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { if (mediaServer == null) { return false; } - JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServer); - if (responseJSON != null) { - JSONArray data = responseJSON.getJSONArray("data"); + ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServer); + if (mediaServerConfig != null) { + List data = mediaServerConfig.getData(); if (data != null && !data.isEmpty()) { ZLMServerConfig zlmServerConfig= JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); return zlmServerConfig.getGeneralMediaServerId().equals(mediaServer.getId()); @@ -92,25 +107,21 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { mediaServer.setServerId(userSetting.getServerId()); mediaServer.setIp(ip); mediaServer.setHttpPort(port); - mediaServer.setFlvPort(port); - mediaServer.setWsFlvPort(port); mediaServer.setSecret(secret); - JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServer); - if (responseJSON == null) { + ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); + if (mediaServerConfigResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接失败"); } - JSONArray data = responseJSON.getJSONArray("data"); - if (data == null) { + List configList = mediaServerConfigResult.getData(); + if (configList == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "读取配置失败"); } - ZLMServerConfig zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); + ZLMServerConfig zlmServerConfig = JSON.parseObject(JSON.toJSONString(configList.get(0)), ZLMServerConfig.class); if (zlmServerConfig == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "读取配置失败"); } mediaServer.setId(zlmServerConfig.getGeneralMediaServerId()); mediaServer.setHttpSSlPort(zlmServerConfig.getHttpSSLport()); - mediaServer.setFlvSSLPort(zlmServerConfig.getHttpSSLport()); - mediaServer.setWsFlvSSLPort(zlmServerConfig.getHttpSSLport()); mediaServer.setRtmpPort(zlmServerConfig.getRtmpPort()); mediaServer.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort()); mediaServer.setRtspPort(zlmServerConfig.getRtspPort()); @@ -133,12 +144,12 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { if (!ObjectUtils.isEmpty(ssrc)) { param.put("ssrc", ssrc); } - JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaInfo, param); - if (jsonObject.getInteger("code") != null && jsonObject.getInteger("code") == 0) { + ZLMResult zlmResult = zlmresTfulUtils.stopSendRtp(mediaInfo, param); + if (zlmResult.getCode() == 0) { log.info("[停止发流] 成功: 参数:{}", JSON.toJSONString(param)); return true; }else { - log.info("停止发流结果: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param)); + log.info("停止发流结果: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); return false; } } @@ -152,9 +163,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { if (!ObjectUtils.isEmpty(ssrc)) { param.put("ssrc", ssrc); } - JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaInfo, param); - if (jsonObject == null || jsonObject.getInteger("code") != 0 ) { - log.error("停止发流失败: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param)); + ZLMResult zlmResult = zlmresTfulUtils.stopSendRtp(mediaInfo, param); + if (zlmResult.getCode() != 0 ) { + log.error("停止发流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); return false; } return true; @@ -163,12 +174,12 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { @Override public boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) { log.info("[zlm-deleteRecordDirectory] 删除磁盘文件, server: {} {}:{}->{}/{}", mediaServer.getId(), app, stream, date, fileName); - JSONObject jsonObject = zlmresTfulUtils.deleteRecordDirectory(mediaServer, app, + ZLMResult zlmResult = zlmresTfulUtils.deleteRecordDirectory(mediaServer, app, stream, date, fileName); - if (jsonObject.getInteger("code") == 0) { + if (zlmResult.getCode() == 0) { return true; }else { - log.info("[zlm-deleteRecordDirectory] 删除磁盘文件错误, server: {} {}:{}->{}/{}, 结果: {}", mediaServer.getId(), app, stream, date, fileName, jsonObject); + log.info("[zlm-deleteRecordDirectory] 删除磁盘文件错误, server: {} {}:{}->{}/{}, 结果: {}", mediaServer.getId(), app, stream, date, fileName, zlmResult); throw new ControllerException(ErrorCode.ERROR100.getCode(), "删除磁盘文件失败"); } } @@ -176,17 +187,17 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { @Override public List getMediaList(MediaServer mediaServer, String app, String stream, String callId) { List streamInfoList = new ArrayList<>(); - JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaServer, app, stream); - if (mediaList != null) { - if (mediaList.getInteger("code") == 0) { - JSONArray dataArray = mediaList.getJSONArray("data"); - if (dataArray == null) { + ZLMResult zlmResult = zlmresTfulUtils.getMediaList(mediaServer, app, stream); + if (zlmResult != null) { + if (zlmResult.getCode() == 0) { + if (zlmResult.getData() == null) { return streamInfoList; } - for (int i = 0; i < dataArray.size(); i++) { - JSONObject mediaJSON = dataArray.getJSONObject(0); + for (int i = 0; i < zlmResult.getData().size(); i++) { + JSONObject mediaJSON = zlmResult.getData().getJSONObject(0); MediaInfo mediaInfo = MediaInfo.getInstance(mediaJSON, mediaServer, userSetting.getServerId()); - StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, mediaInfo.getApp(), mediaInfo.getStream(), mediaInfo, callId, true); + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, mediaInfo.getApp(), + mediaInfo.getStream(), mediaInfo, null, callId, true); if (streamInfo != null) { streamInfoList.add(streamInfo); } @@ -196,155 +207,99 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { return streamInfoList; } - public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String callId, boolean isPlay) { - StreamInfo streamInfoResult = new StreamInfo(); - streamInfoResult.setServerId(userSetting.getServerId()); - streamInfoResult.setStream(stream); - streamInfoResult.setApp(app); - String addr = mediaServer.getStreamIp(); - streamInfoResult.setIp(addr); - streamInfoResult.setMediaServer(mediaServer); - - Map param = new HashMap<>(); - if (!ObjectUtils.isEmpty(callId)) { - param.put("callId", callId); - } - if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { - param.put("originTypeStr", mediaInfo.getOriginTypeStr()); - } - StringBuilder callIdParamBuilder = new StringBuilder(); - if (!param.isEmpty()) { - callIdParamBuilder.append("?"); - for (Map.Entry entry : param.entrySet()) { - callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); - callIdParamBuilder.append("&"); - } - callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); - } - - String callIdParam = callIdParamBuilder.toString(); - - streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); - streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); - String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam); - streamInfoResult.setFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); - streamInfoResult.setWsFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); - streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setHls(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); - - streamInfoResult.setMediaInfo(mediaInfo); - if (mediaInfo != null) { - streamInfoResult.setOriginType(mediaInfo.getOriginType()); - streamInfoResult.setOriginTypeStr(mediaInfo.getOriginTypeStr()); - } - return streamInfoResult; - } - @Override public Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String stream) { - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServer, address, port, stream); - log.info("[TCP主动连接对方] 结果: {}", jsonObject); - return jsonObject.getInteger("code") == 0; + ZLMResult zlmResult = zlmresTfulUtils.connectRtpServer(mediaServer, address, port, stream); + log.info("[TCP主动连接对方] 结果: {}", zlmResult); + return zlmResult.getCode() == 0; } @Override - public void getSnap(MediaServer mediaServer, String streamUrl, int timeoutSec, int expireSec, String path, String fileName) { + public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { + String streamUrl; + if (mediaServer.getRtspPort() != 0) { + streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServer.getRtspPort(), "rtp", stream); + } else { + streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServer.getHttpPort(), "rtp", stream); + } zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName); } @Override public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) { - JSONObject jsonObject = zlmresTfulUtils.getMediaInfo(mediaServer, app, "rtsp", stream); - if (jsonObject.getInteger("code") != 0) { + ZLMResult zlmResult = zlmresTfulUtils.getMediaInfo(mediaServer, app, "rtsp", stream); + if (zlmResult.getCode() != 0) { return null; } - return MediaInfo.getInstance(jsonObject, mediaServer, userSetting.getServerId()); + return MediaInfo.getInstance(zlmResult.getData(), mediaServer, userSetting.getServerId()); } @Override public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) { - JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServer, streamKey); - return jsonObject.getInteger("code") == 0; + ZLMResult zlmResult = zlmresTfulUtils.pauseRtpCheck(mediaServer, streamKey); + return zlmResult.getCode() == 0; } @Override public Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) { - JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServer, streamKey); - return jsonObject.getInteger("code") == 0; + ZLMResult zlmResult = zlmresTfulUtils.resumeRtpCheck(mediaServer, streamKey); + return zlmResult.getCode() == 0; } @Override public String getFfmpegCmd(MediaServer mediaServer, String cmdKey) { - JSONObject jsonObject = zlmresTfulUtils.getMediaServerConfig(mediaServer); - if (jsonObject.getInteger("code") != 0) { + ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); + if (mediaServerConfigResult == null || mediaServerConfigResult.getCode() != 0) { log.warn("[getFfmpegCmd] 获取流媒体配置失败"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取流媒体配置失败"); } - JSONArray dataArray = jsonObject.getJSONArray("data"); - JSONObject mediaServerConfig = dataArray.getJSONObject(0); + List data = mediaServerConfigResult.getData(); + JSONObject mediaServerConfig = data.get(0); if (ObjectUtils.isEmpty(cmdKey)) { cmdKey = "ffmpeg.cmd"; } return mediaServerConfig.getString(cmdKey); } - @Override - public WVPResult addFFmpegSource(MediaServer mediaServer, String srcUrl, String dstUrl, int timeoutMs, boolean enableAudio, boolean enableMp4, String ffmpegCmdKey) { - JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaServer, srcUrl, dstUrl, timeoutMs, enableAudio, enableMp4, ffmpegCmdKey); - if (jsonObject.getInteger("code") != 0) { - log.warn("[getFfmpegCmd] 添加FFMPEG代理失败"); - return WVPResult.fail(ErrorCode.ERROR100.getCode(), "添加FFMPEG代理失败"); - }else { - JSONObject data = jsonObject.getJSONObject("data"); - if (data == null) { - return WVPResult.fail(ErrorCode.ERROR100.getCode(), "代理结果异常: " + jsonObject); - }else { - return WVPResult.success(data.getString("key")); - } - } - } - @Override public WVPResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) { - JSONObject jsonObject = zlmresTfulUtils.addStreamProxy(mediaServer, app, stream, url, enableAudio, enableMp4, rtpType, timeout); - if (jsonObject.getInteger("code") != 0) { + ZLMResult zlmResult = zlmresTfulUtils.addStreamProxy(mediaServer, app, stream, url, enableAudio, enableMp4, rtpType, timeout); + if (zlmResult.getCode() != 0) { return WVPResult.fail(ErrorCode.ERROR100.getCode(), "添加代理失败"); }else { - JSONObject data = jsonObject.getJSONObject("data"); + StreamProxyResult data = zlmResult.getData(); if (data == null) { - return WVPResult.fail(ErrorCode.ERROR100.getCode(), "代理结果异常: " + jsonObject); + return WVPResult.fail(ErrorCode.ERROR100.getCode(), "代理结果异常"); }else { - return WVPResult.success(data.getString("key")); + return WVPResult.success(data.getKey()); } } } @Override public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) { - JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaServer, streamKey); - return jsonObject.getInteger("code") == 0; + ZLMResult flagDataZLMResult = zlmresTfulUtils.delFFmpegSource(mediaServer, streamKey); + return flagDataZLMResult != null && flagDataZLMResult.getCode() == 0; } @Override public Boolean delStreamProxy(MediaServer mediaServer, String streamKey) { - JSONObject jsonObject = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); - return jsonObject.getInteger("code") == 0; + ZLMResult flagDataZLMResult = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); + return flagDataZLMResult != null && flagDataZLMResult.getCode() == 0; } @Override public Map getFFmpegCMDs(MediaServer mediaServer) { Map result = new HashMap<>(); - JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig(mediaServer); - if (mediaServerConfigResuly != null && mediaServerConfigResuly.getInteger("code") == 0 - && mediaServerConfigResuly.getJSONArray("data").size() > 0){ - JSONObject mediaServerConfig = mediaServerConfigResuly.getJSONArray("data").getJSONObject(0); + ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer); + if (mediaServerConfigResult != null && mediaServerConfigResult.getCode() == 0 + && !mediaServerConfigResult.getData().isEmpty()){ + JSONObject jsonObject = mediaServerConfigResult.getData().get(0); - for (String key : mediaServerConfig.keySet()) { + for (String key : jsonObject.keySet()) { if (key.startsWith("ffmpeg.cmd")){ - result.put(key, mediaServerConfig.getString(key)); + result.put(key, jsonObject.getString(key)); } } } @@ -364,6 +319,7 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); param.put("recv_stream_id", sendRtpItem.getReceiveStream()); + param.put("enable_origin_recv_limit", "1"); if (timeout != null) { param.put("close_delay_ms", timeout); } @@ -376,15 +332,13 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { param.put("dst_port", sendRtpItem.getPort()); } - JSONObject jsonObject = zlmServerFactory.startSendRtpPassive(mediaServer, param, null); - if (jsonObject == null || jsonObject.getInteger("code") != 0 ) { - log.error("启动监听TCP被动推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param)); - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + ZLMResult zlmResult = zlmServerFactory.startSendRtpPassive(mediaServer, param, null); + if (zlmResult.getCode() != 0 ) { + log.error("启动监听TCP被动推流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } - log.info("调用ZLM-TCP被动推流接口, 结果: {}", jsonObject); - log.info("启动监听TCP被动推流成功[ {}/{} ],{}->{}:{}, " , sendRtpItem.getApp(), sendRtpItem.getStream(), - jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); - return jsonObject.getInteger("local_port"); + log.info("调用ZLM-TCP被动推流接口成功: 本地端口: {}", zlmResult.getLocal_port()); + return zlmResult.getLocal_port(); } @Override @@ -399,19 +353,41 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); + param.put("enable_origin_recv_limit", "1"); if (!sendRtpItem.isTcp()) { // udp模式下开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp() ? "500" : "0"); } param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); - JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServer, param); - if (jsonObject == null ) { + ZLMResult zlmResult = zlmresTfulUtils.startSendRtp(mediaServer, param); + if (zlmResult == null ) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接zlm失败"); - }else if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + }else if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } - log.info("[推流结果]:{} ,参数: {}",jsonObject, JSONObject.toJSONString(param)); + log.info("[推流结果]:{} ,参数: {}", zlmResult, JSONObject.toJSONString(param)); + } + + @Override + public Integer startSendRtpTalk(MediaServer mediaServer, SendRtpInfo sendRtpItem, Integer timeout) { + Map param = new HashMap<>(12); + param.put("vhost","__defaultVhost__"); + param.put("app", sendRtpItem.getApp()); + param.put("stream", sendRtpItem.getStream()); + param.put("ssrc", sendRtpItem.getSsrc()); + param.put("pt", sendRtpItem.getPt()); + param.put("type", sendRtpItem.isUsePs() ? "1" : "0"); + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); + param.put("recv_stream_id", sendRtpItem.getReceiveStream()); + param.put("enable_origin_recv_limit", "1"); + ZLMResult zlmResult = zlmServerFactory.startSendRtpTalk(mediaServer, param, null); + if (zlmResult.getCode() != 0 ) { + log.error("启动监听TCP被动推流失败: {}, 参数:{}", zlmResult.getMsg(), JSON.toJSONString(param)); + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); + } + log.info("调用ZLM-TCP被动推流接口, 成功 本地端口: {}", zlmResult.getLocal_port()); + return zlmResult.getLocal_port(); } @Override @@ -425,7 +401,7 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { } @Override - public StreamInfo startProxy(MediaServer mediaServer, StreamProxy streamProxy) { + public String startProxy(MediaServer mediaServer, StreamProxy streamProxy) { String dstUrl; if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())) { @@ -463,42 +439,29 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { MediaInfo mediaInfo = getMediaInfo(mediaServer, streamProxy.getApp(), streamProxy.getStream()); if (mediaInfo != null) { - if (mediaInfo.getOriginUrl() != null && mediaInfo.getOriginUrl().equals(streamProxy.getSrcUrl())) { - log.info("[启动拉流代理] 已存在, 直接返回, app: {}, stream: {}", mediaInfo.getApp(), streamProxy.getStream()); - return getStreamInfoByAppAndStream(mediaServer, streamProxy.getApp(), streamProxy.getStream(), mediaInfo, null, true); - } closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream()); } - JSONObject jsonObject = null; + ZLMResult zlmResult = null; if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())){ if (streamProxy.getTimeout() == 0) { streamProxy.setTimeout(15); } - jsonObject = zlmresTfulUtils.addFFmpegSource(mediaServer, streamProxy.getSrcUrl().trim(), dstUrl, + zlmResult = zlmresTfulUtils.addFFmpegSource(mediaServer, streamProxy.getSrcUrl().trim(), dstUrl, streamProxy.getTimeout(), streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getFfmpegCmdKey()); }else { - jsonObject = zlmresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), + zlmResult = zlmresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(), streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout()); } - if (jsonObject == null) { - throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); - }else if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); }else { - JSONObject data = jsonObject.getJSONObject("data"); + StreamProxyResult data = zlmResult.getData(); if (data == null) { - throw new ControllerException(jsonObject.getInteger("code"), "代理结果异常: " + jsonObject); + throw new ControllerException(zlmResult.getCode(), "代理结果异常: " + zlmResult); }else { - streamProxy.setStreamKey(data.getString("key")); - // 由于此时流未注册,手动拼装流信息 - mediaInfo = new MediaInfo(); - mediaInfo.setApp(streamProxy.getApp()); - mediaInfo.setStream(streamProxy.getStream()); - mediaInfo.setOriginType(4); - mediaInfo.setOriginTypeStr("pull"); - return getStreamInfoByAppAndStream(mediaServer, streamProxy.getApp(), streamProxy.getStream(), mediaInfo, null, true); + return data.getKey(); } } } @@ -523,63 +486,197 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { } @Override - public void stopProxy(MediaServer mediaServer, String streamKey) { - JSONObject jsonObject = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); - if (jsonObject == null) { + public void stopProxy(MediaServer mediaServer, String streamKey, String type) { + ZLMResult zlmResult = zlmresTfulUtils.delStreamProxy(mediaServer, streamKey); + if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); - }else if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + }else if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public List listRtpServer(MediaServer mediaServer) { - JSONObject jsonObject = zlmresTfulUtils.listRtpServer(mediaServer); + ZLMResult> zlmResult = zlmresTfulUtils.listRtpServer(mediaServer); List result = new ArrayList<>(); - if (jsonObject == null || jsonObject.getInteger("code") != 0) { + if (zlmResult.getCode() != 0) { return result; } - JSONArray data = jsonObject.getJSONArray("data"); + List data = zlmResult.getData(); if (data == null || data.isEmpty()) { return result; } - for (int i = 0; i < data.size(); i++) { - JSONObject dataJSONObject = data.getJSONObject(i); - result.add(dataJSONObject.getString("stream_id")); + for (RtpServerResult datum : data) { + result.add(datum.getStream_id()); } return result; } @Override - public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { - JSONObject jsonObject = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath); - if (jsonObject == null) { + public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback callback) { + String buildApp = "mp4_record"; + String buildStream = app + "_" + stream + "_" + fileName + "_" + RandomStringUtils.randomAlphabetic(6).toLowerCase(); + + Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId()); + subscribe.addSubscribe(hook, (hookData) -> { + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true); + if (callback != null) { + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + } + }); + + ZLMResult zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, filePath); + + if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } - if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); + } + } + + @Override + public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback callback) { + String buildApp = "mp4_record"; + String buildStream = app + "_" + stream + "_" + date; + MediaInfo mediaInfo = getMediaInfo(mediaServer, buildApp, buildStream); + if (mediaInfo != null) { + if (callback != null) { + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null, null, true); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + } + return; + } + + Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId()); + subscribe.addSubscribe(hook, (hookData) -> { + StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true); + if (callback != null) { + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + } + }); + + ZLMResult zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, dateDir); + + if (zlmResult == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); + } + if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { - JSONObject jsonObject = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, schema); - if (jsonObject == null) { + ZLMResult zlmResult = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, schema); + if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } - if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } @Override public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) { - JSONObject jsonObject = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, schema); - if (jsonObject == null) { + ZLMResult zlmResult = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, schema); + if (zlmResult == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败"); } - if (jsonObject.getInteger("code") != 0) { - throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg")); + if (zlmResult.getCode() != 0) { + throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg()); } } + + @Override + public DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, RecordInfo recordInfo) { + + // 将filePath作为独立参数传入,避免%符号解析问题 + String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s"; + + DownloadFileInfo info = new DownloadFileInfo(); + + // filePath作为第4个参数 + info.setHttpPath(String.format(pathTemplate, + "http", + mediaServerItem.getStreamIp(), + mediaServerItem.getHttpPort(), + recordInfo.getFilePath())); + + // 同样作为第4个参数 + if (mediaServerItem.getHttpSSlPort() > 0) { + info.setHttpsPath(String.format(pathTemplate, + "https", + mediaServerItem.getStreamIp(), + mediaServerItem.getHttpSSlPort(), + recordInfo.getFilePath())); + } + return info; + } + + @Override + public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) { + StreamInfo streamInfoResult = new StreamInfo(); + streamInfoResult.setStream(stream); + streamInfoResult.setApp(app); + if (addr == null) { + addr = mediaServer.getStreamIp(); + } + + streamInfoResult.setIp(addr); + if (mediaInfo != null) { + streamInfoResult.setServerId(mediaInfo.getServerId()); + }else { + streamInfoResult.setServerId(userSetting.getServerId()); + } + + streamInfoResult.setMediaServer(mediaServer); + Map param = new HashMap<>(); + if (!ObjectUtils.isEmpty(callId)) { + param.put("callId", callId); + } + if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) { + param.put("originTypeStr", mediaInfo.getOriginTypeStr()); + } + StringBuilder callIdParamBuilder = new StringBuilder(); + if (!param.isEmpty()) { + callIdParamBuilder.append("?"); + for (Map.Entry entry : param.entrySet()) { + callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue()); + callIdParamBuilder.append("&"); + } + callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1); + } + + String callIdParam = callIdParamBuilder.toString(); + + streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam); + streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam); + + String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam); + streamInfoResult.setFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); + streamInfoResult.setWsFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile); + + String mp4File = String.format("%s/%s.live.mp4%s", app, stream, callIdParam); + streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File); + streamInfoResult.setWsMp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File); + + streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + streamInfoResult.setWsHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + + streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + streamInfoResult.setWsTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam); + + streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); + + streamInfoResult.setMediaInfo(mediaInfo); + + if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { + String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); + mediaServer.setTranscodeSuffix(null); + StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); + streamInfoResult.setTranscodeStream(transcodeStreamInfo); + } + return streamInfoResult; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaServerStatusManager.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaServerStatusManager.java index 37e6e846d..0281ffe45 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaServerStatusManager.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaServerStatusManager.java @@ -1,7 +1,6 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; @@ -9,6 +8,7 @@ import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; +import com.genersoft.iot.vmp.media.zlm.dto.ZLMResult; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerKeepaliveEvent; import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerStartEvent; @@ -23,6 +23,7 @@ import org.springframework.util.ObjectUtils; import java.io.File; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -136,13 +137,13 @@ public class ZLMMediaServerStatusManager { continue; } log.info("[ZLM-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); - JSONObject responseJson = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); + ZLMResult> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); ZLMServerConfig zlmServerConfig = null; - if (responseJson == null) { + if (mediaServerConfigResult == null) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); continue; } - JSONArray data = responseJson.getJSONArray("data"); + List data = mediaServerConfigResult.getData(); if (data == null || data.isEmpty()) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); }else { @@ -158,14 +159,14 @@ public class ZLMMediaServerStatusManager { continue; } log.info("[ZLM-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); - JSONObject responseJson = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); + ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); ZLMServerConfig zlmServerConfig = null; - if (responseJson == null) { + if (mediaServerConfig == null) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); continue; } - JSONArray data = responseJson.getJSONArray("data"); + List data = mediaServerConfig.getData(); if (data == null || data.isEmpty()) { log.info("[ZLM-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis()); @@ -190,8 +191,8 @@ public class ZLMMediaServerStatusManager { eventPublisher.mediaServerOnlineEventPublish(mediaServerItem); if(mediaServerItem.isAutoConfig()) { if (config == null) { - JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); - JSONArray data = responseJSON.getJSONArray("data"); + ZLMResult> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); + List data = mediaServerConfig.getData(); if (data != null && !data.isEmpty()) { config = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class); } @@ -247,6 +248,7 @@ public class ZLMMediaServerStatusManager { }else { mediaServerItem.setTranscodeSuffix(zlmServerConfig.getTranscodeSuffix()); } + mediaServerItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort()); mediaServerItem.setHookAliveInterval(10F); } @@ -257,7 +259,6 @@ public class ZLMMediaServerStatusManager { String hookPrefix = String.format("%s://%s:%s%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort, (serverServletContextPath == null || "/".equals(serverServletContextPath)) ? "" : serverServletContextPath); Map param = new HashMap<>(); - param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline if (mediaServerItem.getRtspPort() != 0) { param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); } @@ -289,8 +290,6 @@ public class ZLMMediaServerStatusManager { // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) { param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-")); - }else { - param.put("rtp_proxy.port", mediaServerItem.getRtpProxyPort()); } if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) { @@ -300,9 +299,9 @@ public class ZLMMediaServerStatusManager { param.put("record.appName", recordPathFile.getName()); } - JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param); + ZLMResult zlmResult = zlmresTfulUtils.setServerConfig(mediaServerItem, param); - if (responseJSON != null && responseJSON.getInteger("code") == 0) { + if (zlmResult != null && zlmResult.getCode() == 0) { if (restart) { log.info("[媒体服务节点] 设置成功,开始重启以保证配置生效 {} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index a4774d973..3414eacc7 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -1,8 +1,13 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.zlm.dto.*; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.logging.HttpLoggingInterceptor; @@ -12,10 +17,13 @@ import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.ConnectException; import java.net.SocketTimeoutException; +import java.net.URLEncoder; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -28,7 +36,10 @@ public class ZLMRESTfulUtils { public interface RequestCallback{ - void run(JSONObject response); + void run(String response); + } + public interface ResultCallback{ + void run(ZLMResult response); } private OkHttpClient getClient(){ @@ -62,26 +73,22 @@ public class ZLMRESTfulUtils { } - public JSONObject sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback) { - return sendPost(mediaServerItem, api, param, callback, null); + public String sendPost(MediaServer mediaServer, String api, Map param, RequestCallback callback) { + return sendPost(mediaServer, api, param, callback, null); } - public JSONObject sendPost(MediaServer mediaServerItem, String api, Map param, RequestCallback callback, Integer readTimeOut) { + public String sendPost(MediaServer mediaServer, String api, Map param, RequestCallback callback, Integer readTimeOut) { OkHttpClient client = getClient(readTimeOut); - if (mediaServerItem == null) { + if (mediaServer == null) { return null; } - String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); - JSONObject responseJSON = new JSONObject(); - //-2自定义流媒体 调用错误码 - responseJSON.put("code",-2); - responseJSON.put("msg","流媒体调用失败"); - + String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api); + String result = null; FormBody.Builder builder = new FormBody.Builder(); - builder.add("secret",mediaServerItem.getSecret()); - if (param != null && param.keySet().size() > 0) { + builder.add("secret",mediaServer.getSecret()); + if (param != null && !param.isEmpty()) { for (String key : param.keySet()){ if (param.get(key) != null) { builder.add(key, param.get(key).toString()); @@ -101,8 +108,7 @@ public class ZLMRESTfulUtils { if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { - String responseStr = responseBody.string(); - responseJSON = JSON.parseObject(responseStr); + result = responseBody.string(); } }else { response.close(); @@ -131,7 +137,7 @@ public class ZLMRESTfulUtils { if (response.isSuccessful()) { try { String responseStr = Objects.requireNonNull(response.body()).string(); - callback.run(JSON.parseObject(responseStr)); + callback.run(responseStr); } catch (IOException e) { log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } @@ -158,20 +164,18 @@ public class ZLMRESTfulUtils { }); } - - - return responseJSON; + return result; } - public void sendGetForImg(MediaServer mediaServerItem, String api, Map params, String targetPath, String fileName) { - String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); + public void sendGetForImg(MediaServer mediaServer, String api, Map params, String targetPath, String fileName) { + String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api); HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { return; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); - httpBuilder.addQueryParameter("secret", mediaServerItem.getSecret()); + httpBuilder.addQueryParameter("secret", mediaServer.getSecret()); if (params != null) { for (Map.Entry param : params.entrySet()) { httpBuilder.addQueryParameter(param.getKey(), param.getValue().toString()); @@ -217,7 +221,7 @@ public class ZLMRESTfulUtils { } } - public JSONObject isMediaOnline(MediaServer mediaServerItem, String app, String stream, String schema){ + public ZLMResult isMediaOnline(MediaServer mediaServer, String app, String stream, String schema){ Map param = new HashMap<>(); if (app != null) { param.put("app",app); @@ -229,10 +233,20 @@ public class ZLMRESTfulUtils { param.put("schema",schema); } param.put("vhost","__defaultVhost__"); - return sendPost(mediaServerItem, "isMediaOnline", param, null); + String response = sendPost(mediaServer, "isMediaOnline", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject getMediaList(MediaServer mediaServerItem, String app, String stream, String schema, RequestCallback callback){ + public ZLMResult getMediaList(MediaServer mediaServer, String app, String stream, String schema, ResultCallback callback){ Map param = new HashMap<>(); if (app != null) { param.put("app",app); @@ -244,102 +258,336 @@ public class ZLMRESTfulUtils { param.put("schema",schema); } param.put("vhost","__defaultVhost__"); - return sendPost(mediaServerItem, "getMediaList",param, callback); + String response = sendPost(mediaServer, "getMediaList",param, (responseStr -> { + if (callback == null) { + return; + } + if (responseStr == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + ZLMResult zlmResult = JSON.parseObject(responseStr, new TypeReference>() {}); + if (zlmResult == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + callback.run(zlmResult); + } + + } + })); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject getMediaList(MediaServer mediaServerItem, String app, String stream){ - return getMediaList(mediaServerItem, app, stream,null, null); + public ZLMResult getMediaList(MediaServer mediaServer, String app, String stream){ + return getMediaList(mediaServer, app, stream,null, null); } - public JSONObject getMediaList(MediaServer mediaServerItem, RequestCallback callback){ - return sendPost(mediaServerItem, "getMediaList",null, callback); - } - - public JSONObject getMediaInfo(MediaServer mediaServerItem, String app, String schema, String stream){ + public ZLMResult getMediaInfo(MediaServer mediaServer, String app, String schema, String stream){ Map param = new HashMap<>(); param.put("app",app); param.put("schema",schema); param.put("stream",stream); param.put("vhost","__defaultVhost__"); - return sendPost(mediaServerItem, "getMediaInfo",param, null); + + String response = sendPost(mediaServer, "getMediaInfo",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + JSONObject jsonObject = JSON.parseObject(response); + if (jsonObject == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = new ZLMResult<>(); + zlmResult.setCode(0); + zlmResult.setData(jsonObject); + return zlmResult; + } + } } - public JSONObject getRtpInfo(MediaServer mediaServerItem, String stream_id){ + public ZLMResult getRtpInfo(MediaServer mediaServer, String stream_id){ Map param = new HashMap<>(); param.put("stream_id",stream_id); - return sendPost(mediaServerItem, "getRtpInfo",param, null); + String response = sendPost(mediaServer, "getRtpInfo",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject addFFmpegSource(MediaServer mediaServerItem, String src_url, String dst_url, Integer timeout_sec, + public ZLMResult addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec, boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){ - log.info(src_url); - log.info(dst_url); + try { + src_url = URLEncoder.encode(src_url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); + } + Map param = new HashMap<>(); param.put("src_url", src_url); param.put("dst_url", dst_url); param.put("timeout_ms", timeout_sec*1000); param.put("enable_mp4", enable_mp4); param.put("ffmpeg_cmd_key", ffmpeg_cmd_key); - return sendPost(mediaServerItem, "addFFmpegSource",param, null); + + String response = sendPost(mediaServer, "addFFmpegSource",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject delFFmpegSource(MediaServer mediaServerItem, String key){ + public ZLMResult delFFmpegSource(MediaServer mediaServer, String key){ Map param = new HashMap<>(); param.put("key", key); - return sendPost(mediaServerItem, "delFFmpegSource",param, null); + + String response = sendPost(mediaServer, "delFFmpegSource",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject delStreamProxy(MediaServer mediaServerItem, String key){ + public ZLMResult delStreamProxy(MediaServer mediaServer, String key){ Map param = new HashMap<>(); param.put("key", key); - return sendPost(mediaServerItem, "delStreamProxy",param, null); + String response = sendPost(mediaServer, "delStreamProxy",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject getMediaServerConfig(MediaServer mediaServerItem){ - return sendPost(mediaServerItem, "getServerConfig",null, null); + public ZLMResult> getMediaServerConfig(MediaServer mediaServer ){ + + String response = sendPost(mediaServer, "getServerConfig",null, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject setServerConfig(MediaServer mediaServerItem, Map param){ - return sendPost(mediaServerItem,"setServerConfig",param, null); + public ZLMResult setServerConfig(MediaServer mediaServer, Map param){ + String response = sendPost(mediaServer, "setServerConfig",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject openRtpServer(MediaServer mediaServerItem, Map param){ - return sendPost(mediaServerItem, "openRtpServer",param, null); + public ZLMResult openRtpServer(MediaServer mediaServer, Map param){ + String response = sendPost(mediaServer, "openRtpServer",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject closeRtpServer(MediaServer mediaServerItem, Map param) { - return sendPost(mediaServerItem, "closeRtpServer",param, null); + public ZLMResult closeRtpServer(MediaServer mediaServer, Map param) { + String response = sendPost(mediaServer, "closeRtpServer",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public void closeRtpServer(MediaServer mediaServerItem, Map param, RequestCallback callback) { - sendPost(mediaServerItem, "closeRtpServer",param, callback); + public void closeRtpServer(MediaServer mediaServer, Map param, ResultCallback callback) { + sendPost(mediaServer, "closeRtpServer",param, (response -> { + if (callback == null) { + return; + } + if (response == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + callback.run(JSON.parseObject(response, ZLMResult.class)); + } + })); + } - public JSONObject listRtpServer(MediaServer mediaServerItem) { - return sendPost(mediaServerItem, "listRtpServer",null, null); + public ZLMResult> listRtpServer(MediaServer mediaServer) { + String response = sendPost(mediaServer, "listRtpServer",null, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject startSendRtp(MediaServer mediaServerItem, Map param) { - return sendPost(mediaServerItem, "startSendRtp",param, null); + public ZLMResult startSendRtp(MediaServer mediaServer, Map param) { + String response = sendPost(mediaServer, "startSendRtp",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject startSendRtpPassive(MediaServer mediaServerItem, Map param) { - return sendPost(mediaServerItem, "startSendRtpPassive",param, null); + public ZLMResult startSendRtpPassive(MediaServer mediaServer, Map param) { + String response = sendPost(mediaServer, "startSendRtpPassive",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject startSendRtpPassive(MediaServer mediaServerItem, Map param, RequestCallback callback) { - return sendPost(mediaServerItem, "startSendRtpPassive",param, callback); + public ZLMResult startSendRtpPassive(MediaServer mediaServer, Map param, ResultCallback callback) { + String response = sendPost(mediaServer, "startSendRtpPassive",param, (responseStr -> { + if (callback == null) { + return; + } + if (responseStr == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + ZLMResult zlmResult = JSON.parseObject(responseStr, ZLMResult.class); + if (zlmResult == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + callback.run(zlmResult); + } + } + })); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject stopSendRtp(MediaServer mediaServerItem, Map param) { - return sendPost(mediaServerItem, "stopSendRtp",param, null); + public ZLMResult startSendRtpTalk(MediaServer mediaServer, Map param, ResultCallback callback) { + String response = sendPost(mediaServer, "startSendRtpTalk",param, (responseStr -> { + if (callback == null) { + return; + } + if (responseStr == null) { + callback.run(ZLMResult.getFailForMediaServer()); + }else { + callback.run(JSON.parseObject(responseStr, ZLMResult.class)); + } + })); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject restartServer(MediaServer mediaServerItem) { - return sendPost(mediaServerItem, "restartServer",null, null); + public ZLMResult stopSendRtp(MediaServer mediaServer, Map param) { + String response = sendPost(mediaServer, "stopSendRtp",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject addStreamProxy(MediaServer mediaServerItem, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) { + public ZLMResult restartServer(MediaServer mediaServer) { + String response = sendPost(mediaServer, "restartServer",null, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } + } + + public ZLMResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) { + try { + url = URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败"); + } Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); @@ -351,94 +599,198 @@ public class ZLMRESTfulUtils { param.put("timeout_sec", timeOut); // 拉流重试次数,默认为3 param.put("retry_count", 3); - return sendPost(mediaServerItem, "addStreamProxy",param, null, 20); + + String response = sendPost(mediaServer, "addStreamProxy",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject closeStreams(MediaServer mediaServerItem, String app, String stream) { + public ZLMResult closeStreams(MediaServer mediaServer, String app, String stream) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("force", 1); - return sendPost(mediaServerItem, "close_streams",param, null); + + String response = sendPost(mediaServer, "close_streams",param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, new TypeReference>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject getAllSession(MediaServer mediaServerItem) { - return sendPost(mediaServerItem, "getAllSession",null, null); + public ZLMResult> getAllSession(MediaServer mediaServer) { + String response = sendPost(mediaServer, "getAllSession",null, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult> zlmResult = JSON.parseObject(response, new TypeReference>>() {}); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public void kickSessions(MediaServer mediaServerItem, String localPortSStr) { + public void kickSessions(MediaServer mediaServer, String localPortSStr) { Map param = new HashMap<>(); param.put("local_port", localPortSStr); - sendPost(mediaServerItem, "kick_sessions",param, null); + sendPost(mediaServer, "kick_sessions",param, null); } - public void getSnap(MediaServer mediaServerItem, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) { + public void getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) { Map param = new HashMap<>(3); param.put("url", streamUrl); param.put("timeout_sec", timeout_sec); param.put("expire_sec", expire_sec); - sendGetForImg(mediaServerItem, "getSnap", param, targetPath, fileName); + param.put("async", 1); + sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName); } - public JSONObject pauseRtpCheck(MediaServer mediaServerItem, String streamId) { + public ZLMResult pauseRtpCheck(MediaServer mediaServer, String streamId) { Map param = new HashMap<>(1); param.put("stream_id", streamId); - return sendPost(mediaServerItem, "pauseRtpCheck",param, null); + String response = sendPost(mediaServer, "pauseRtpCheck", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject resumeRtpCheck(MediaServer mediaServerItem, String streamId) { + public ZLMResult resumeRtpCheck(MediaServer mediaServer, String streamId) { Map param = new HashMap<>(1); param.put("stream_id", streamId); - return sendPost(mediaServerItem, "resumeRtpCheck",param, null); + String response = sendPost(mediaServer, "resumeRtpCheck", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject connectRtpServer(MediaServer mediaServerItem, String dst_url, int dst_port, String stream_id) { + public ZLMResult connectRtpServer(MediaServer mediaServer, String dst_url, int dst_port, String stream_id) { Map param = new HashMap<>(1); param.put("dst_url", dst_url); param.put("dst_port", dst_port); param.put("stream_id", stream_id); - return sendPost(mediaServerItem, "connectRtpServer",param, null); + String response = sendPost(mediaServer, "connectRtpServer", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject updateRtpServerSSRC(MediaServer mediaServerItem, String streamId, String ssrc) { + public ZLMResult updateRtpServerSSRC(MediaServer mediaServer, String streamId, String ssrc) { Map param = new HashMap<>(1); param.put("ssrc", ssrc); param.put("stream_id", streamId); - return sendPost(mediaServerItem, "updateRtpServerSSRC",param, null); + + String response = sendPost(mediaServer, "updateRtpServerSSRC", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject deleteRecordDirectory(MediaServer mediaServerItem, String app, String stream, String date, String fileName) { + public ZLMResult deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("period", date); param.put("name", fileName); - return sendPost(mediaServerItem, "deleteRecordDirectory",param, null); + String response = sendPost(mediaServer, "deleteRecordDirectory", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { + public ZLMResult loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("file_path", datePath); param.put("file_repeat", "0"); - return sendPost(mediaServer, "loadMP4File",param, null); + String response = sendPost(mediaServer, "loadMP4File", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject setRecordSpeed(MediaServer mediaServer, String app, String stream, int speed, String schema) { + public ZLMResult setRecordSpeed(MediaServer mediaServer, String app, String stream, int speed, String schema) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); param.put("stream", stream); param.put("speed", speed); param.put("schema", schema); - return sendPost(mediaServer, "setRecordSpeed",param, null); + String response = sendPost(mediaServer, "setRecordSpeed", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } - public JSONObject seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { + public ZLMResult seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) { Map param = new HashMap<>(1); param.put("vhost", "__defaultVhost__"); param.put("app", app); @@ -446,6 +798,17 @@ public class ZLMRESTfulUtils { BigDecimal bigDecimal = new BigDecimal(stamp); param.put("stamp", bigDecimal); param.put("schema", schema); - return sendPost(mediaServer, "seekRecordStamp",param, null); + + String response = sendPost(mediaServer, "seekRecordStamp", param, null); + if (response == null) { + return ZLMResult.getFailForMediaServer(); + }else { + ZLMResult zlmResult = JSON.parseObject(response, ZLMResult.class); + if (zlmResult == null) { + return ZLMResult.getFailForMediaServer(); + }else { + return zlmResult; + } + } } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java index fee0ccabc..4349445d3 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java @@ -1,9 +1,10 @@ package com.genersoft.iot.vmp.media.zlm; -import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.zlm.dto.ZLMResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,22 +29,22 @@ public class ZLMServerFactory { * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。 * @return */ - public int createRTPServer(MediaServer mediaServerItem, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) { + public int createRTPServer(MediaServer mediaServerItem, String app, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { int result = -1; // 查询此rtp server 是否已经存在 - JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); - if(rtpInfo.getInteger("code") == 0){ - if (rtpInfo.getBoolean("exist")) { - result = rtpInfo.getInteger("local_port"); + ZLMResult rtpInfoResult = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); + if(rtpInfoResult.getCode() == 0){ + if (rtpInfoResult.getExist() != null && rtpInfoResult.getExist()) { + result = rtpInfoResult.getLocal_port(); if (result == 0) { // 此时说明rtpServer已经创建但是流还没有推上来 // 此时重新打开rtpServer Map param = new HashMap<>(); param.put("stream_id", streamId); - JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); - if (jsonObject != null ) { - if (jsonObject.getInteger("code") == 0) { - return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode); + ZLMResult zlmResult = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); + if (zlmResult != null ) { + if (zlmResult.getCode() == 0) { + return createRTPServer(mediaServerItem, streamId, app, ssrc, port,onlyAuto, reUsePort,disableAudio, tcpMode); }else { log.warn("[开启rtpServer], 重启RtpServer错误"); } @@ -51,7 +52,7 @@ public class ZLMServerFactory { } return result; } - }else if(rtpInfo.getInteger("code") == -2){ + }else if(rtpInfoResult.getCode() == -2){ return result; } @@ -61,7 +62,12 @@ public class ZLMServerFactory { tcpMode = 0; } param.put("tcp_mode", tcpMode); + param.put("app", app); param.put("stream_id", streamId); + if (disableAudio != null) { + param.put("only_track", disableAudio?2:0); + } + if (reUsePort != null) { param.put("re_use_port", reUsePort?"1":"0"); } @@ -78,12 +84,12 @@ public class ZLMServerFactory { param.put("ssrc", ssrc); } - JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param); - if (openRtpServerResultJson != null) { - if (openRtpServerResultJson.getInteger("code") == 0) { - result= openRtpServerResultJson.getInteger("port"); + ZLMResult zlmResult = zlmresTfulUtils.openRtpServer(mediaServerItem, param); + if (zlmResult != null) { + if (zlmResult.getCode() == 0) { + result= zlmResult.getPort(); }else { - log.error("创建RTP Server 失败 {}: ", openRtpServerResultJson.getString("msg")); + log.error("创建RTP Server 失败 {}: ", zlmResult.getMsg()); } }else { // 检查ZLM状态 @@ -97,13 +103,12 @@ public class ZLMServerFactory { if (serverItem !=null){ Map param = new HashMap<>(); param.put("stream_id", streamId); - JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param); - log.info("关闭RTP Server " + jsonObject); - if (jsonObject != null ) { - if (jsonObject.getInteger("code") == 0) { - result = jsonObject.getInteger("hit") >= 1; + ZLMResult zlmResult = zlmresTfulUtils.closeRtpServer(serverItem, param); + if (zlmResult != null ) { + if (zlmResult.getCode() == 0) { + result = zlmResult.getHit() >= 1; }else { - log.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); + log.error("关闭RTP Server 失败: " + zlmResult.getMsg()); } }else { // 检查ZLM状态 @@ -115,84 +120,66 @@ public class ZLMServerFactory { public void closeRtpServer(MediaServer serverItem, String streamId, CommonCallback callback) { if (serverItem == null) { - callback.run(false); + if (callback != null) { + callback.run(false); + } return; } Map param = new HashMap<>(); param.put("stream_id", streamId); - zlmresTfulUtils.closeRtpServer(serverItem, param, jsonObject -> { - if (jsonObject != null ) { - if (jsonObject.getInteger("code") == 0) { - callback.run(jsonObject.getInteger("hit") == 1); - return; - }else { - log.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); + zlmresTfulUtils.closeRtpServer(serverItem, param, zlmResult -> { + if (zlmResult.getCode() == 0) { + if (callback != null) { + callback.run(zlmResult.getHit() >= 1); } + return; }else { - // 检查ZLM状态 - log.error("关闭RTP Server 失败: 请检查ZLM服务"); + log.error("关闭RTP Server 失败: " + zlmResult.getMsg()); + } + if (callback != null) { + callback.run(false); } - callback.run(false); }); - - } /** * 调用zlm RESTFUL API —— startSendRtp */ - public JSONObject startSendRtpStream(MediaServer mediaServerItem, Mapparam) { + public ZLMResult startSendRtpStream(MediaServer mediaServerItem, Mapparam) { return zlmresTfulUtils.startSendRtp(mediaServerItem, param); } /** * 调用zlm RESTFUL API —— startSendRtpPassive */ - public JSONObject startSendRtpPassive(MediaServer mediaServerItem, Mapparam) { + public ZLMResult startSendRtpPassive(MediaServer mediaServerItem, Mapparam) { return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param); } - public JSONObject startSendRtpPassive(MediaServer mediaServerItem, Mapparam, ZLMRESTfulUtils.RequestCallback callback) { + public ZLMResult startSendRtpPassive(MediaServer mediaServerItem, Map param, ZLMRESTfulUtils.ResultCallback callback) { return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback); } + public ZLMResult startSendRtpTalk(MediaServer mediaServer, Map param, ZLMRESTfulUtils.ResultCallback callback) { + return zlmresTfulUtils.startSendRtpTalk(mediaServer, param, callback); + } + /** * 查询待转推的流是否就绪 */ public Boolean isStreamReady(MediaServer mediaServerItem, String app, String streamId) { - JSONObject mediaInfo = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId); - if (mediaInfo == null || (mediaInfo.getInteger("code") == -2)) { + ZLMResult zlmResult = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId); + if (zlmResult == null || zlmResult.getCode() == -2) { return null; } - return (mediaInfo.getInteger("code") == 0 - && mediaInfo.getJSONArray("data") != null - && mediaInfo.getJSONArray("data").size() > 0); + ZLMResult result = (ZLMResult) zlmResult; + return (result.getCode() == 0 + && result.getData() != null + && !result.getData().isEmpty()); } - /** - * 查询转推的流是否有其它观看者 - * @param streamId - * @return - */ - public int totalReaderCount(MediaServer mediaServerItem, String app, String streamId) { - JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtsp", streamId); - if (mediaInfo == null) { - return 0; - } - Integer code = mediaInfo.getInteger("code"); - if (code < 0) { - log.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); - return -1; - } - if ( code == 0 && mediaInfo.getBoolean("online") != null && ! mediaInfo.getBoolean("online")) { - log.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); - return -1; - } - return mediaInfo.getInteger("totalReaderCount"); - } - - public JSONObject startSendRtp(MediaServer mediaInfo, SendRtpInfo sendRtpItem) { + public ZLMResult startSendRtp(MediaServer mediaInfo, SendRtpInfo sendRtpItem) { String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; log.info("rtp/{}开始推流, 目标={}:{},SSRC={}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); Map param = new HashMap<>(12); @@ -213,45 +200,43 @@ public class ZLMServerFactory { return null; } // 如果是非严格模式,需要关闭端口占用 - JSONObject startSendRtpStreamResult = null; + ZLMResult zlmResult = null; if (sendRtpItem.getLocalPort() != 0) { if (sendRtpItem.isTcpActive()) { - startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param); + zlmResult = startSendRtpPassive(mediaInfo, param); }else { param.put("is_udp", is_Udp); param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); - startSendRtpStreamResult = startSendRtpStream(mediaInfo, param); + zlmResult = startSendRtpStream(mediaInfo, param); } }else { if (sendRtpItem.isTcpActive()) { - startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param); + zlmResult = startSendRtpPassive(mediaInfo, param); }else { param.put("is_udp", is_Udp); param.put("dst_url", sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); - startSendRtpStreamResult = startSendRtpStream(mediaInfo, param); + zlmResult = startSendRtpStream(mediaInfo, param); } } - return startSendRtpStreamResult; + return zlmResult; } public Boolean updateRtpServerSSRC(MediaServer mediaServerItem, String streamId, String ssrc) { boolean result = false; - JSONObject jsonObject = zlmresTfulUtils.updateRtpServerSSRC(mediaServerItem, streamId, ssrc); - if (jsonObject == null) { - log.error("[更新RTPServer] 失败: 请检查ZLM服务"); - } else if (jsonObject.getInteger("code") == 0) { + ZLMResult zlmResult = zlmresTfulUtils.updateRtpServerSSRC(mediaServerItem, streamId, ssrc); + if (zlmResult.getCode() == 0) { result= true; log.info("[更新RTPServer] 成功"); } else { - log.error("[更新RTPServer] 失败: {}, streamId:{},ssrc:{}->\r\n{}",jsonObject.getString("msg"), - streamId, ssrc, jsonObject); + log.error("[更新RTPServer] 失败: {}, streamId:{},ssrc:{}", zlmResult.getMsg(), + streamId, ssrc); } return result; } - public JSONObject stopSendRtpStream(MediaServer mediaServerItem, SendRtpInfo sendRtpItem) { + public ZLMResult stopSendRtpStream(MediaServer mediaServerItem, SendRtpInfo sendRtpItem) { Map param = new HashMap<>(); param.put("vhost", "__defaultVhost__"); param.put("app", sendRtpItem.getApp()); @@ -259,4 +244,6 @@ public class ZLMServerFactory { param.put("ssrc", sendRtpItem.getSsrc()); return zlmresTfulUtils.stopSendRtp(mediaServerItem, param); } + + } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/FlagData.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/FlagData.java new file mode 100644 index 000000000..a5bbdf7f5 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/FlagData.java @@ -0,0 +1,8 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import lombok.Data; + +@Data +public class FlagData { + private boolean flag; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/RtpServerResult.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/RtpServerResult.java new file mode 100644 index 000000000..f7e61d61c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/RtpServerResult.java @@ -0,0 +1,10 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import lombok.Data; + +@Data +public class RtpServerResult { + private Integer port; + private String stream_id; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/SessionData.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/SessionData.java new file mode 100644 index 000000000..fb8a33b1f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/SessionData.java @@ -0,0 +1,14 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import lombok.Data; + +@Data +public class SessionData { + private String id; + private String local_ip; + private Integer local_port; + private String peer_ip; + private Integer peer_port; + private String typeid; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyResult.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyResult.java new file mode 100644 index 000000000..7276582eb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyResult.java @@ -0,0 +1,9 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import lombok.Data; + +@Data +public class StreamProxyResult { + + private String key; +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMResult.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMResult.java new file mode 100644 index 000000000..5dd18386c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMResult.java @@ -0,0 +1,58 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import lombok.Data; + +@Data +public class ZLMResult { + private int code; + private String msg; + private T data; + + + private Boolean online; + private Boolean exist; + private String peer_ip; + private Integer peer_port; + private String local_ip; + private Integer local_port; + private Integer changed; + private Integer port; + private Integer hit; + + public static ZLMResult getFailForMediaServer() { + ZLMResult zlmResult = new ZLMResult<>(); + zlmResult.setCode(-2); + zlmResult.setMsg("流媒体调用失败"); + return zlmResult; + } + + public static ZLMResult getMediaServer(int code, String msg) { + return getMediaServer(code, msg, null); + } + + public static ZLMResult getMediaServer(int code, String msg, T data) { + ZLMResult zlmResult = new ZLMResult<>(); + zlmResult.setCode(code); + zlmResult.setMsg(msg); + zlmResult.setData(data); + return zlmResult; + } + + @Override + public String toString() { + return "ZLMResult{" + + "code=" + code + + ", msg='" + msg + '\'' + + ", data=" + data + + (online != null ? (", online=" + online) : "") + + (exist != null ? (", exist=" + exist) : "") + + (peer_ip != null ? (", peer_ip=" + peer_ip) : "") + + (peer_port != null ? (", peer_port=" + peer_port) : "") + + (local_ip != null ? (", local_ip=" + local_ip) : "") + + (local_port != null ? (", local_port=" + local_port) : "") + + (changed != null ? (", changed=" + changed) : "") + + (port != null ? (", port=" + port) : "") + + (hit != null ? (", hit=" + hit) : "") + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java b/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java index 6fdda6df6..af32e77cb 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java @@ -59,11 +59,13 @@ public interface ICloudRecordService { /** * 加载录像文件,形成录像流 */ - void loadRecord(String app, String stream, String date, ErrorCallback callback); + void loadMP4FileForDate(String app, String stream, String date, ErrorCallback callback); void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema); void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema); void deleteFileByIds(Set ids); + + void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java b/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java index 384da8789..01e452a54 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.service.bean; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; +import com.genersoft.iot.vmp.media.event.media.MediaRecordProcessEvent; import com.genersoft.iot.vmp.utils.MediaServerUtils; import lombok.Data; @@ -90,14 +91,14 @@ public class CloudRecordItem { CloudRecordItem cloudRecordItem = new CloudRecordItem(); cloudRecordItem.setApp(param.getApp()); cloudRecordItem.setStream(param.getStream()); - cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()*1000); + cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()); cloudRecordItem.setFileName(param.getRecordInfo().getFileName()); cloudRecordItem.setFolder(param.getRecordInfo().getFolder()); cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize()); cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath()); cloudRecordItem.setMediaServerId(param.getMediaServer().getId()); - cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen() * 1000); - cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000); + cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen()); + cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen())); Map paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams()); if (paramsMap.get("callId") != null) { cloudRecordItem.setCallId(paramsMap.get("callId")); @@ -105,4 +106,14 @@ public class CloudRecordItem { return cloudRecordItem; } + public static CloudRecordItem getInstance(MediaRecordProcessEvent event) { + CloudRecordItem cloudRecordItem = new CloudRecordItem(); + cloudRecordItem.setApp(event.getApp()); + cloudRecordItem.setStream(event.getStream()); + cloudRecordItem.setFileName(event.getFileName()); + cloudRecordItem.setMediaServerId(event.getMediaServer().getId()); + cloudRecordItem.setTimeLen(event.getCurrentFileDuration() * 1000); + return cloudRecordItem; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java index 602e18467..c8e7b158d 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java @@ -1,5 +1,8 @@ package com.genersoft.iot.vmp.service.bean; +import lombok.Data; + +@Data public class DownloadFileInfo { private String httpPath; @@ -7,35 +10,4 @@ public class DownloadFileInfo { private String httpDomainPath; private String httpsDomainPath; - public String getHttpPath() { - return httpPath; - } - - public void setHttpPath(String httpPath) { - this.httpPath = httpPath; - } - - public String getHttpsPath() { - return httpsPath; - } - - public void setHttpsPath(String httpsPath) { - this.httpsPath = httpsPath; - } - - public String getHttpDomainPath() { - return httpDomainPath; - } - - public void setHttpDomainPath(String httpDomainPath) { - this.httpDomainPath = httpDomainPath; - } - - public String getHttpsDomainPath() { - return httpsDomainPath; - } - - public void setHttpsDomainPath(String httpsDomainPath) { - this.httpsDomainPath = httpsDomainPath; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java index faf8323f8..70a84c0bf 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java @@ -18,7 +18,9 @@ public enum InviteErrorCode { ERROR_FOR_SIP_SENDING_FAILED(-10, "命令发送失败"), ERROR_FOR_ASSIST_NOT_READY(-11, "没有可用的assist服务"), ERROR_FOR_PARAMETER_ERROR(-13, "参数异常"), - ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR(-14, "TCP主动连接失败"); + ERROR_FOR_TCP_ACTIVE_CONNECTION_REFUSED_ERROR(-14, "TCP主动连接失败"), + ERROR_FOR_FINISH(-20, "已结束"), + ; private final int code; private final String msg; diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java index 150a8a33e..a9bf5412f 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java @@ -5,11 +5,8 @@ import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; -import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; -import com.genersoft.iot.vmp.media.event.hook.Hook; -import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; -import com.genersoft.iot.vmp.media.event.hook.HookType; +import com.genersoft.iot.vmp.media.bean.RecordInfo; import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; @@ -21,7 +18,6 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; -import com.genersoft.iot.vmp.utils.CloudRecordUtils; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.PageHelper; @@ -37,7 +33,10 @@ import org.springframework.util.Assert; import java.io.File; import java.time.LocalDate; import java.time.ZoneOffset; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @Slf4j @Service @@ -61,9 +60,6 @@ public class CloudRecordServiceImpl implements ICloudRecordService { @Autowired private IRedisRpcPlayService redisRpcPlayService; - @Autowired - private HookSubscribe subscribe; - @Override public PageInfo getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List mediaServerItems, String callId, Boolean ascOrder) { @@ -255,9 +251,10 @@ public class CloudRecordServiceImpl implements ICloudRecordService { if (!userSetting.getServerId().equals(recordItem.getServerId())) { return redisRpcPlayService.getRecordPlayUrl(recordItem.getServerId(), recordId); } - String filePath = recordItem.getFilePath(); - MediaServer mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId()); - return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath); + + MediaServer mediaServer = mediaServerService.getOne(recordItem.getMediaServerId()); + + return mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(recordItem)); } @Override @@ -284,7 +281,36 @@ public class CloudRecordServiceImpl implements ICloudRecordService { } @Override - public void loadRecord(String app, String stream, String date, ErrorCallback callback) { + public void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback callback) { + + CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(cloudRecordId); + if (recordItem == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "无录像"); + } + String mediaServerId = recordItem.getMediaServerId(); + MediaServer mediaServer = mediaServerService.getOne(mediaServerId); + if (mediaServer == null) { + log.warn("[云端录像] 播放 未找到录制的流媒体,将自动选择低负载流媒体使用"); + mediaServer = mediaServerService.getMediaServerForMinimumLoad(null); + } + if (mediaServer == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "无可用流媒体"); + } + String fileName = recordItem.getFileName().substring(0 , recordItem.getFileName().indexOf(".")); + String filePath = recordItem.getFilePath(); +// if (filePath != null) { +// fileName = filePath.substring(0, filePath.lastIndexOf("/")); +// } + mediaServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, ((code, msg, streamInfo) -> { + if (code == ErrorCode.SUCCESS.getCode()) { + streamInfo.setDuration(recordItem.getTimeLen()); + } + callback.run(code, msg, streamInfo); + })); + } + + @Override + public void loadMP4FileForDate(String app, String stream, String date, ErrorCallback callback) { long startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(date + " 00:00:00"); long endTimestamp = startTimestamp + 24 * 60 * 60 * 1000; @@ -297,26 +323,13 @@ public class CloudRecordServiceImpl implements ICloudRecordService { if (mediaServer == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId); } - String buildApp = "mp4_record"; - String buildStream = app + "_" + stream + "_" + date; - MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, buildApp, buildStream); - if (mediaInfo != null) { - if (callback != null) { - StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null); - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); - } - return; - } + String dateDir = null; + String filePath = recordItemList.get(0).getFilePath(); + if (filePath != null) { + dateDir = filePath.substring(0, filePath.lastIndexOf("/")); + } + mediaServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback); - Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServerId); - subscribe.addSubscribe(hook, (hookData) -> { - StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null); - if (callback != null) { - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); - } - }); - String dateDir = recordItemList.get(0).getFilePath().substring(0, recordItemList.get(0).getFilePath().lastIndexOf("/")); - mediaServerService.loadMP4File(mediaServer, buildApp, buildStream, dateDir); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java index 6f5d990d8..28a720c64 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -11,11 +11,15 @@ import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; +import com.genersoft.iot.vmp.jt1078.bean.JTMediaStreamType; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; +import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.service.IRecordPlanService; +import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; @@ -60,6 +64,16 @@ public class MediaServiceImpl implements IMediaService { @Autowired private SipInviteSessionManager sessionManager; + @Autowired + private Ijt1078Service ijt1078Service; + + @Autowired + private Ijt1078PlayService jt1078PlayService; + + @Autowired + private ISendRtpServerService sendRtpServerService; + + @Autowired private IRecordPlanService recordPlanService; @@ -81,13 +95,25 @@ public class MediaServiceImpl implements IMediaService { @Override public ResultForOnPublish authenticatePublish(MediaServer mediaServer, String app, String stream, String params) { // 推流鉴权的处理 - if (!"rtp".equals(app)) { + if (!"rtp".equals(app) && !"1078".equals(app) ) { if ("talk".equals(app) && stream.endsWith("_talk")) { ResultForOnPublish result = new ResultForOnPublish(); result.setEnable_mp4(false); result.setEnable_audio(true); return result; } + if ("jt_talk".equals(app) && stream.endsWith("_talk")) { + ResultForOnPublish result = new ResultForOnPublish(); + result.setEnable_mp4(false); + result.setEnable_audio(true); + return result; + } + if ("mp4_record".equals(app) ) { + ResultForOnPublish result = new ResultForOnPublish(); + result.setEnable_mp4(false); + result.setEnable_audio(true); + return result; + } StreamProxy streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, stream); if (streamProxyItem != null) { ResultForOnPublish result = new ResultForOnPublish(); @@ -146,7 +172,7 @@ public class MediaServiceImpl implements IMediaService { inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); if (inviteInfo != null) { result.setStream_replace(inviteInfo.getStream()); - log.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream()); + log.info("[HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream()); stream = inviteInfo.getStream(); } } @@ -221,22 +247,36 @@ public class MediaServiceImpl implements IMediaService { result = userSetting.getStreamOnDemand(); // 国标流, 点播/录像回放/录像下载 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, stream); - // 点播 - if (inviteInfo != null && inviteInfo.getStatus() == InviteSessionStatus.ok) { - // 录像下载 - if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { - return false; - } - DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(inviteInfo.getChannelId()); - if (deviceChannel == null) { - return false; + if (inviteInfo != null) { + if (inviteInfo.getStatus() == InviteSessionStatus.ok){ + // 录像下载 + if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { + return false; + } + DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(inviteInfo.getChannelId()); + if (deviceChannel == null) { + return false; + } } return result; + } + }else if ("1078".equals(app)) { + // 判断是否是1078播放类型 + JTMediaStreamType jtMediaStreamType = ijt1078Service.checkStreamFromJt(stream); + if (jtMediaStreamType != null) { + String[] streamParamArray = stream.split("_"); + if (jtMediaStreamType.equals(JTMediaStreamType.PLAY)) { + jt1078PlayService.stopPlay(streamParamArray[0], Integer.parseInt(streamParamArray[1])); + }else if (jtMediaStreamType.equals(JTMediaStreamType.PLAYBACK)) { + jt1078PlayService.stopPlayback(streamParamArray[0], Integer.parseInt(streamParamArray[1])); + } }else { return false; } - } else if ("talk".equals(app) || "broadcast".equals(app)) { + }else if ("talk".equals(app) || "broadcast".equals(app)) { return false; + } else if ("mp4_record".equals(app)) { + return true; } else { // 非国标流 推流/拉流代理 // 拉流代理 @@ -260,5 +300,6 @@ public class MediaServiceImpl implements IMediaService { return false; } } + return result; } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java index db1035e3e..69ed485a3 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java @@ -16,24 +16,23 @@ public interface IRedisRpcPlayService { void playback(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback); + void playbackPause(String serverId, String streamId); + + void playbackResume(String serverId, String streamId); + void download(String serverId, Integer channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback callback); void queryRecordInfo(String serverId, Integer channelId, String startTime, String endTime, ErrorCallback callback); - void pauseRtp(String serverId, String streamId); - - void resumeRtp(String serverId, String streamId); - String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2); void playPush(String serverId, Integer id, ErrorCallback callback); - StreamInfo playProxy(String serverId, int id); + void playProxy(String serverId, int id, ErrorCallback callback); void stopProxy(String serverId, int id); DownloadFileInfo getRecordPlayUrl(String serverId, Integer recordId); - AudioBroadcastResult audioBroadcast(String serverId, String deviceId, String channelDeviceId, Boolean broadcastMode); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcService.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcService.java index 549af39b7..dec5fcf9d 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcService.java @@ -6,6 +6,8 @@ import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import java.util.List; + public interface IRedisRpcService { SendRtpInfo getSendRtpItem(String callId); @@ -63,5 +65,5 @@ public interface IRedisRpcService { WVPResult deviceInfo(String serverId, Device device); - WVPResult queryPreset(String serverId, Device device, String channelId); + WVPResult> queryPreset(String serverId, Device device, String channelId); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcChannelPlayController.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcChannelPlayController.java index 84b444084..acd8e8136 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcChannelPlayController.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcChannelPlayController.java @@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.service.redisMsg.control; -import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.UserSetting; @@ -10,9 +9,7 @@ import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; -import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo; -import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IPTZService; @@ -81,7 +78,7 @@ public class RedisRpcChannelPlayController extends RpcController { InviteMessageInfo inviteInfo = new InviteMessageInfo(); inviteInfo.setSessionName("Play"); - channelPlayService.start(channel, inviteInfo, null, (code, msg, data) ->{ + channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); @@ -118,9 +115,9 @@ public class RedisRpcChannelPlayController extends RpcController { response.setBody("param error"); return response; } - + try { - channelService.queryRecordInfo(channel, startTime, endTime, (code, msg, data) ->{ + channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(code); response.setBody(data); @@ -134,15 +131,15 @@ public class RedisRpcChannelPlayController extends RpcController { response.setStatusCode(ErrorCode.ERROR100.getCode()); response.setBody(e.getMessage()); } - + return null; } /** * 暂停录像回放 */ - @RedisRpcMapping("pauseRtp") - public RedisRpcResponse pauseRtp(RedisRpcRequest request) { + @RedisRpcMapping("playbackPause") + public RedisRpcResponse playbackPause(RedisRpcRequest request) { String streamId = request.getParam().toString(); RedisRpcResponse response = request.getResponse(); @@ -153,7 +150,7 @@ public class RedisRpcChannelPlayController extends RpcController { } try { - channelPlayService.pauseRtp(streamId); +// channelPlayService.playbackPause(streamId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); @@ -166,8 +163,8 @@ public class RedisRpcChannelPlayController extends RpcController { /** * 恢复录像回放 */ - @RedisRpcMapping("resumeRtp") - public RedisRpcResponse resumeRtp(RedisRpcRequest request) { + @RedisRpcMapping("playbackResume") + public RedisRpcResponse playbackResume(RedisRpcRequest request) { String streamId = request.getParam().toString(); RedisRpcResponse response = request.getResponse(); @@ -178,7 +175,7 @@ public class RedisRpcChannelPlayController extends RpcController { } try { - channelPlayService.resumeRtp(streamId); +// channelPlayService.playbackResume(streamId); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (ControllerException e) { response.setStatusCode(ErrorCode.ERROR100.getCode()); @@ -216,7 +213,7 @@ public class RedisRpcChannelPlayController extends RpcController { return response; } try { - channelPlayService.stopPlay(type, channel, stream); + channelPlayService.stopInvite(type, channel, stream); response.setStatusCode(ErrorCode.SUCCESS.getCode()); }catch (Exception e){ response.setStatusCode(Response.SERVER_INTERNAL_ERROR); @@ -253,7 +250,7 @@ public class RedisRpcChannelPlayController extends RpcController { inviteInfo.setSessionName("Playback"); inviteInfo.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)); inviteInfo.setStopTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime)); - channelPlayService.start(channel, inviteInfo, null, (code, msg, data) ->{ + channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); @@ -296,7 +293,7 @@ public class RedisRpcChannelPlayController extends RpcController { inviteInfo.setStartTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)); inviteInfo.setStopTime(DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime)); inviteInfo.setDownloadSpeed(downloadSpeed + ""); - channelPlayService.start(channel, inviteInfo, null, (code, msg, data) ->{ + channelPlayService.startInvite(channel, inviteInfo, null, (code, msg, data) ->{ if (code == InviteErrorCode.SUCCESS.getCode()) { response.setStatusCode(ErrorCode.SUCCESS.getCode()); response.setBody(data); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcSendRtpController.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcSendRtpController.java index fa8180252..d809d61ea 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcSendRtpController.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcSendRtpController.java @@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.sip.message.Response; - @Component @Slf4j @RedisRpcController("sendRtp") @@ -61,7 +59,7 @@ public class RedisRpcSendRtpController extends RpcController { } // 自平台内容 int localPort = sendRtpServerService.getNextPort(mediaServerItem); - if (localPort == 0) { + if (localPort <= 0) { log.info("[redis-rpc] getSendRtpItem->服务器端口资源不足" ); RedisRpcResponse response = request.getResponse(); response.setStatusCode(ErrorCode.SUCCESS.getCode()); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamProxyController.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamProxyController.java index 55764c531..4a688c3df 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamProxyController.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamProxyController.java @@ -1,7 +1,6 @@ package com.genersoft.iot.vmp.service.redisMsg.control; import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig; import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage; @@ -63,10 +62,13 @@ public class RedisRpcStreamProxyController extends RpcController { response.setBody("param error"); return response; } - StreamInfo streamInfo = streamProxyPlayService.startProxy(streamProxy); - response.setStatusCode(ErrorCode.SUCCESS.getCode()); - response.setBody(JSONObject.toJSONString(streamInfo)); - return response; + streamProxyPlayService.startProxy(streamProxy, (code, msg, streamInfo) -> { + response.setStatusCode(code); + response.setBody(JSONObject.toJSONString(streamInfo)); + sendResponse(response); + }); + + return null; } /** diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java index 5f369efaf..0e2e2e8ce 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java @@ -121,8 +121,8 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService { } @Override - public void pauseRtp(String serverId, String streamId) { - RedisRpcRequest request = buildRequest("channel/pauseRtp", streamId); + public void playbackPause(String serverId, String streamId) { + RedisRpcRequest request = buildRequest("channel/playbackPause", streamId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 5, TimeUnit.SECONDS); if (response == null) { @@ -135,8 +135,8 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService { } @Override - public void resumeRtp(String serverId, String streamId) { - RedisRpcRequest request = buildRequest("channel/resumeRtp", streamId); + public void playbackResume(String serverId, String streamId) { + RedisRpcRequest request = buildRequest("channel/playbackResume", streamId); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 5, TimeUnit.SECONDS); if (response == null) { @@ -212,13 +212,20 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService { } @Override - public StreamInfo playProxy(String serverId, int id) { + public void playProxy(String serverId, int id, ErrorCallback callback) { RedisRpcRequest request = buildRequest("streamProxy/play", id); + request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); - if (response != null && response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { - return JSON.parseObject(response.getBody().toString(), StreamInfo.class); + if (response == null) { + callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); + }else { + if (response.getStatusCode() == ErrorCode.SUCCESS.getCode()) { + StreamInfo streamInfo = JSON.parseObject(response.getBody().toString(), StreamInfo.class); + callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); + }else { + callback.run(response.getStatusCode(), response.getBody().toString(), null); + } } - return null; } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java index 531ef6aaf..408380fe4 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @@ -406,7 +407,7 @@ public class RedisRpcServiceImpl implements IRedisRpcService { } @Override - public WVPResult queryPreset(String serverId, Device device, String channelId) { + public WVPResult> queryPreset(String serverId, Device device, String channelId) { JSONObject jsonObject = new JSONObject(); jsonObject.put("device", device.getDeviceId()); jsonObject.put("channelId", channelId); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java index 87ac2059d..30f6e5111 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java @@ -127,6 +127,18 @@ public interface CloudRecordServiceMapper { " ") CloudRecordItem queryOne(@Param("id") Integer id); + @Select(" ") + CloudRecordItem getListByFileName(@Param("app") String app, @Param("stream") String stream, @Param("fileName") String fileName); + + @Update(" ") + void updateTimeLen(@Param("id") int id, @Param("time") Long time, @Param("endTime") long endTime); + @Select(" "}) diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java new file mode 100644 index 000000000..06d278eb1 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java @@ -0,0 +1,53 @@ +package com.genersoft.iot.vmp.streamPush.service.impl; + +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.enums.ChannelDataType; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.bean.Platform; +import com.genersoft.iot.vmp.gb28181.bean.PlayException; +import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.sip.message.Response; + +@Slf4j +@Service(ChannelDataType.PLAY_SERVICE + ChannelDataType.STREAM_PUSH) +public class SourcePlayServiceForStreamPushImpl implements ISourcePlayService { + + @Autowired + private IStreamPushPlayService playService; + + @Override + public void play(CommonGBChannel channel, Platform platform, Boolean record, ErrorCallback callback) { + String serverGBId = null; + String platformName = null; + if (platform != null) { + // 推流 + serverGBId = platform.getServerGBId(); + platformName = platform.getName(); + } + // 推流 + try { + playService.start(channel.getDataDeviceId(), callback, serverGBId, platformName); + }catch (PlayException e) { + callback.run(e.getCode(), e.getMsg(), null); + }catch (Exception e) { + log.error("[点播推流通道失败] 通道: {}({})", channel.getGbName(), channel.getGbDeviceId(), e); + callback.run(Response.BUSY_HERE, "busy here", null); + } + } + + @Override + public void stopPlay(CommonGBChannel channel, String stream) { + // 推流 + try { + playService.stop(channel.getDataDeviceId()); + }catch (Exception e) { + log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/CivilCodeUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/CivilCodeUtil.java index 06f5910fd..33c3e47d9 100644 --- a/src/main/java/com/genersoft/iot/vmp/utils/CivilCodeUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/CivilCodeUtil.java @@ -32,6 +32,10 @@ public enum CivilCodeUtil { civilCodeMap.put(civilCodePo.getCode(), civilCodePo); } + public CivilCodePo get(String code) { + return civilCodeMap.get(code); + } + public CivilCodePo getParentCode(String code) { if (code.length() > 8) { return null; diff --git a/src/main/java/com/genersoft/iot/vmp/utils/CloudRecordUtils.java b/src/main/java/com/genersoft/iot/vmp/utils/CloudRecordUtils.java deleted file mode 100644 index 10cb620b9..000000000 --- a/src/main/java/com/genersoft/iot/vmp/utils/CloudRecordUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.genersoft.iot.vmp.utils; - -import com.genersoft.iot.vmp.media.bean.MediaServer; -import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; - -public class CloudRecordUtils { - - public static DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, String filePath) { - DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); - - String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=" + filePath; - - downloadFileInfo.setHttpPath(String.format(pathTemplate, "http", mediaServerItem.getStreamIp(), - mediaServerItem.getHttpPort())); - - if (mediaServerItem.getHttpSSlPort() > 0) { - downloadFileInfo.setHttpsPath(String.format(pathTemplate, "https", mediaServerItem.getStreamIp(), - mediaServerItem.getHttpSSlPort())); - } - return downloadFileInfo; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java index 565dec1f4..4d97d7ed9 100755 --- a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java @@ -50,6 +50,8 @@ public class DateUtil { * wvp内部统一时间格式 */ public static final String URL_PATTERN = "yyyyMMddHHmmss"; + public static final String PATTERN1078 = "yyMMddHHmmss"; + public static final String PATTERN1078Date = "yyyyMMdd"; /** * 日期格式 @@ -66,11 +68,17 @@ public class DateUtil { public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter DateFormatter = DateTimeFormatter.ofPattern(date_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static final DateTimeFormatter urlFormatter = DateTimeFormatter.ofPattern(URL_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); + public static final DateTimeFormatter formatter1078 = DateTimeFormatter.ofPattern(PATTERN1078, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); + public static final DateTimeFormatter formatter1078date = DateTimeFormatter.ofPattern(PATTERN1078Date, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static String yyyy_MM_dd_HH_mm_ssToISO8601(@NotNull String formatTime) { return formatterISO8601.format(formatter.parse(formatTime)); } + public static String yyyy_MM_dd_HH_mm_ssToUrl(@NotNull String formatTime) { + return urlFormatter.format(formatter.parse(formatTime)); + } + public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) { // 三种日期格式都尝试,为了兼容不同厂家的日期格式 if (verification(formatTime, formatterCompatibleISO8601)) { @@ -86,6 +94,15 @@ public class DateUtil { public static String urlToyyyy_MM_dd_HH_mm_ss(String formatTime) { return formatter.format(urlFormatter.parse(formatTime)); } + public static String yyyy_MM_dd_HH_mm_ssTo1078(String formatTime) { + return formatter1078.format(formatter.parse(formatTime)); + } + public static String jt1078Toyyyy_MM_dd_HH_mm_ss(String formatTime) { + return formatter.format(formatter1078.parse(formatTime)); + } + public static String jt1078dateToyyyy_MM_dd(String formatTime) { + return DateFormatter.format(formatter1078date.parse(formatTime)); + } /** * yyyy_MM_dd_HH_mm_ss 转时间戳 @@ -134,6 +151,18 @@ public class DateUtil { return formatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr))); } + /** + * yyyy_MM_dd_HH_mm_ss 转时间戳(毫秒) + * + * @param formatTime + * @return + */ + public static long urlToTimestampMs(String formatTime) { + TemporalAccessor temporalAccessor = urlFormatter.parse(formatTime); + Instant instant = Instant.from(temporalAccessor); + return instant.toEpochMilli(); + } + /** * 时间戳 转 yyyy_MM_dd */ diff --git a/src/main/java/com/genersoft/iot/vmp/utils/IpPortUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/IpPortUtil.java new file mode 100644 index 000000000..2ce0a5568 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/utils/IpPortUtil.java @@ -0,0 +1,90 @@ +package com.genersoft.iot.vmp.utils; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class IpPortUtil { + + /** + * 拼接IP和端口 + * @param ip IP地址字符串 + * @param port 端口号字符串 + * @return 拼接后的字符串 + * @throws IllegalArgumentException 如果IP地址无效或端口无效 + */ + public static String concatenateIpAndPort(String ip, String port) { + if (port == null || port.isEmpty()) { + throw new IllegalArgumentException("端口号不能为空"); + } + + // 验证端口是否为有效数字 + try { + int portNum = Integer.parseInt(port); + if (portNum < 0 || portNum > 65535) { + throw new IllegalArgumentException("端口号必须在0-65535范围内"); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("端口号必须是有效数字", e); + } + + try { + InetAddress inetAddress = InetAddress.getByName(ip); + + if (inetAddress instanceof Inet6Address) { + // IPv6地址需要加上方括号 + return "[" + ip + "]:" + port; + } else { + // IPv4地址直接拼接 + return ip + ":" + port; + } + } catch (UnknownHostException e) { + throw new IllegalArgumentException("无效的IP地址: " + ip, e); + } + } + + // 测试用例 + public static void main(String[] args) { + // IPv4测试 + String ipv4 = "192.168.1.1"; + String port1 = "8080"; + System.out.println(concatenateIpAndPort(ipv4, port1)); // 输出: 192.168.1.1:8080 + + // IPv6测试 + String ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + String port2 = "80"; + System.out.println(concatenateIpAndPort(ipv6, port2)); // 输出: [2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80 + + // 压缩格式IPv6测试 + String ipv6Compressed = "2001:db8::1"; + System.out.println(concatenateIpAndPort(ipv6Compressed, port2)); // 输出: [2001:db8::1]:80 + + // 无效IP测试 + try { + System.out.println(concatenateIpAndPort("invalid.ip", "1234")); + } catch (IllegalArgumentException e) { + System.out.println("捕获到预期异常: " + e.getMessage()); + } + + // 无效端口测试 - 非数字 + try { + System.out.println(concatenateIpAndPort(ipv4, "abc")); + } catch (IllegalArgumentException e) { + System.out.println("捕获到预期异常: " + e.getMessage()); + } + + // 无效端口测试 - 超出范围 + try { + System.out.println(concatenateIpAndPort(ipv4, "70000")); + } catch (IllegalArgumentException e) { + System.out.println("捕获到预期异常: " + e.getMessage()); + } + + // 空端口测试 + try { + System.out.println(concatenateIpAndPort(ipv4, "")); + } catch (IllegalArgumentException e) { + System.out.println("捕获到预期异常: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java index 53b8be605..c0a0dc301 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java @@ -11,6 +11,7 @@ public enum ErrorCode { ERROR403(403, "无权限操作"), ERROR486(486, "超时或无响应"), ERROR401(401, "请登录后重新请求"), + ERROR408(408, "请求超时"), ERROR500(500, "系统异常"); private final int code; diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java index e484dafa6..ff7df7552 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java @@ -4,7 +4,9 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +@Data @Schema(description = "流信息") public class StreamContent { @@ -95,6 +97,9 @@ public class StreamContent { @Schema(description = "结束时间") private String endTime; + @Schema(description = "时长(回放时使用)") + private Double duration; + @Schema(description = "文件下载地址(录像下载使用)") private DownloadFileInfo downLoadFilePath; @@ -103,6 +108,9 @@ public class StreamContent { private double progress; + @Schema(description = "拉流代理返回的KEY") + private String key; + public StreamContent(StreamInfo streamInfo) { if (streamInfo == null) { return; @@ -180,6 +188,8 @@ public class StreamContent { this.startTime = streamInfo.getStartTime(); this.endTime = streamInfo.getEndTime(); this.progress = streamInfo.getProgress(); + this.duration = streamInfo.getDuration(); + this.key = streamInfo.getKey(); if (streamInfo.getDownLoadFilePath() != null) { this.downLoadFilePath = streamInfo.getDownLoadFilePath(); @@ -189,259 +199,4 @@ public class StreamContent { } } - public StreamContent getTranscodeStream() { - return transcodeStream; - } - - public void setTranscodeStream(StreamContent transcodeStream) { - this.transcodeStream = transcodeStream; - } - - public String getApp() { - return app; - } - - public void setApp(String app) { - this.app = app; - } - - public String getStream() { - return stream; - } - - public void setStream(String stream) { - this.stream = stream; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getFlv() { - return flv; - } - - public void setFlv(String flv) { - this.flv = flv; - } - - public String getHttps_flv() { - return https_flv; - } - - public void setHttps_flv(String https_flv) { - this.https_flv = https_flv; - } - - public String getWs_flv() { - return ws_flv; - } - - public void setWs_flv(String ws_flv) { - this.ws_flv = ws_flv; - } - - public String getWss_flv() { - return wss_flv; - } - - public void setWss_flv(String wss_flv) { - this.wss_flv = wss_flv; - } - - public String getFmp4() { - return fmp4; - } - - public void setFmp4(String fmp4) { - this.fmp4 = fmp4; - } - - public String getHttps_fmp4() { - return https_fmp4; - } - - public void setHttps_fmp4(String https_fmp4) { - this.https_fmp4 = https_fmp4; - } - - public String getWs_fmp4() { - return ws_fmp4; - } - - public void setWs_fmp4(String ws_fmp4) { - this.ws_fmp4 = ws_fmp4; - } - - public String getWss_fmp4() { - return wss_fmp4; - } - - public void setWss_fmp4(String wss_fmp4) { - this.wss_fmp4 = wss_fmp4; - } - - public String getHls() { - return hls; - } - - public void setHls(String hls) { - this.hls = hls; - } - - public String getHttps_hls() { - return https_hls; - } - - public void setHttps_hls(String https_hls) { - this.https_hls = https_hls; - } - - public String getWs_hls() { - return ws_hls; - } - - public void setWs_hls(String ws_hls) { - this.ws_hls = ws_hls; - } - - public String getWss_hls() { - return wss_hls; - } - - public void setWss_hls(String wss_hls) { - this.wss_hls = wss_hls; - } - - public String getTs() { - return ts; - } - - public void setTs(String ts) { - this.ts = ts; - } - - public String getHttps_ts() { - return https_ts; - } - - public void setHttps_ts(String https_ts) { - this.https_ts = https_ts; - } - - public String getWs_ts() { - return ws_ts; - } - - public void setWs_ts(String ws_ts) { - this.ws_ts = ws_ts; - } - - public String getWss_ts() { - return wss_ts; - } - - public void setWss_ts(String wss_ts) { - this.wss_ts = wss_ts; - } - - public String getRtmp() { - return rtmp; - } - - public void setRtmp(String rtmp) { - this.rtmp = rtmp; - } - - public String getRtmps() { - return rtmps; - } - - public void setRtmps(String rtmps) { - this.rtmps = rtmps; - } - - public String getRtsp() { - return rtsp; - } - - public void setRtsp(String rtsp) { - this.rtsp = rtsp; - } - - public String getRtsps() { - return rtsps; - } - - public void setRtsps(String rtsps) { - this.rtsps = rtsps; - } - - public String getRtc() { - return rtc; - } - - public void setRtc(String rtc) { - this.rtc = rtc; - } - - public String getRtcs() { - return rtcs; - } - - public void setRtcs(String rtcs) { - this.rtcs = rtcs; - } - - public String getMediaServerId() { - return mediaServerId; - } - - public void setMediaServerId(String mediaServerId) { - this.mediaServerId = mediaServerId; - } - - public MediaInfo getMediaInfo() { - return mediaInfo; - } - - public void setMediaInfo(MediaInfo mediaInfo) { - this.mediaInfo = mediaInfo; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - - public double getProgress() { - return progress; - } - - public void setProgress(double progress) { - this.progress = progress; - } - - public DownloadFileInfo getDownLoadFilePath() { - return downLoadFilePath; - } - - public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) { - this.downLoadFilePath = downLoadFilePath; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/SystemConfigInfo.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/SystemConfigInfo.java index 20a046cda..838e70670 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/SystemConfigInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/SystemConfigInfo.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.vmanager.bean; import com.genersoft.iot.vmp.common.VersionPo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.jt1078.config.JT1078Config; import lombok.Data; @Data @@ -12,6 +13,7 @@ public class SystemConfigInfo { private SipConfig sip; private UserSetting addOn; private VersionPo version; + private JT1078Config jt1078Config; } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java index 303517465..953392f9d 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java @@ -253,17 +253,17 @@ public class CloudRecordController { @Operation(summary = "加载录像文件形成播放地址") @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) - @Parameter(name = "date", description = "日期, 例如 2025-04-10", required = true) + @Parameter(name = "cloudRecordId", description = "云端录像ID", required = true) public DeferredResult> loadRecord( HttpServletRequest request, @RequestParam(required = true) String app, @RequestParam(required = true) String stream, - @RequestParam(required = true) String date + @RequestParam(required = true) int cloudRecordId ) { DeferredResult> result = new DeferredResult<>(); result.onTimeout(()->{ - log.info("[加载录像文件超时] app={}, stream={}, date={}", app, stream, date); + log.info("[加载录像文件超时] app={}, stream={}, cloudRecordId={}", app, stream, cloudRecordId); WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("加载录像文件超时"); @@ -304,7 +304,7 @@ public class CloudRecordController { result.setResult(wvpResult); }; - cloudRecordService.loadRecord(app, stream, date, callback); + cloudRecordService.loadMP4File(app, stream, cloudRecordId, callback); return result; } @@ -312,6 +312,7 @@ public class CloudRecordController { @GetMapping("/seek") @Operation(summary = "定位录像播放到制定位置") @Parameter(name = "mediaServerId", description = "使用的节点Id", required = true) + @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "seek", description = "要定位的时间位置,从录像开始的时间算起", required = true) public void seekRecord( @@ -331,6 +332,7 @@ public class CloudRecordController { @GetMapping("/speed") @Operation(summary = "设置录像播放速度") @Parameter(name = "mediaServerId", description = "使用的节点Id", required = true) + @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流ID", required = true) @Parameter(name = "speed", description = "要设置的录像倍速", required = true) public void setRecordSpeed( diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java index 6098218c1..d4f6de168 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java @@ -4,7 +4,6 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.SystemAllInfo; import com.genersoft.iot.vmp.common.VersionPo; -import com.genersoft.iot.vmp.common.enums.ChannelDataType; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.VersionInfo; @@ -12,6 +11,7 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; +import com.genersoft.iot.vmp.jt1078.config.JT1078Config; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; @@ -45,7 +45,10 @@ import oshi.software.os.OperatingSystem; import jakarta.servlet.http.HttpServletRequest; import java.io.File; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; @SuppressWarnings("rawtypes") @Tag(name = "服务控制") @@ -67,6 +70,9 @@ public class ServerController { @Autowired private UserSetting userSetting; + @Autowired + private JT1078Config jt1078Config; + @Autowired private IDeviceService deviceService; @@ -196,6 +202,7 @@ public class ServerController { systemConfigInfo.setSip(sipConfig); systemConfigInfo.setAddOn(userSetting); systemConfigInfo.setServerPort(serverPort); + systemConfigInfo.setJt1078Config(jt1078Config); return systemConfigInfo; } @@ -334,20 +341,6 @@ public class ServerController { return result; } - @GetMapping(value = "/channel/datatype") - @ResponseBody - @Operation(summary = "获取系统接入的数据类型", security = @SecurityRequirement(name = JwtUtils.HEADER)) - public List> getDataType() { - List> result = new LinkedList<>(); - for (ChannelDataType item : ChannelDataType.values()) { - Map map = new LinkedHashMap<>(); - map.put("key", item.desc); - map.put("value", item.value); - result.add(map); - } - return result; - } - /** * 单位转换 */ diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 4bdc10d28..700d9ce36 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -107,4 +107,6 @@ user-settings: record-sip: true # 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 stream-on-demand: true + # 是否返回Date属性,true:不返回,避免摄像头通过该参数自动校时,false:返回,摄像头可能会根据该时间校时 + disable-date-header: false diff --git a/src/main/resources/配置详情.yml b/src/main/resources/配置详情.yml index 2fec05146..22868f195 100644 --- a/src/main/resources/配置详情.yml +++ b/src/main/resources/配置详情.yml @@ -88,6 +88,9 @@ server: certificate: xx.pem # 私钥文件 certificate-private-key: xx.key + # protocols: TLSv1.2 + # ciphers: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + # 作为28181服务器的配置 sip: @@ -120,6 +123,13 @@ sip: # 命令发送等待回复的超时时间, 单位:毫秒 timeout: 1000 +# 做为JT1078服务器的配置 +ftp: + #[必须修改] 是否开启1078的服务 + enable: true + port: 2121 + passive-ports: 10000-10500 + # 做为JT1078服务器的配置 jt1078: #[必须修改] 是否开启1078的服务 @@ -129,6 +139,7 @@ jt1078: #[可选] 设备鉴权的密码 password: admin123 + #zlm 默认服务器配置 media: # [必须修改] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId diff --git a/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java b/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java index 8986f91e6..2a48961bf 100644 --- a/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java +++ b/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java @@ -20,7 +20,7 @@ public class JT1078ServerTest { public static void main(String[] args) { System.out.println("Starting jt1078 server..."); - TcpServer tcpServer = new TcpServer(21078); + TcpServer tcpServer = new TcpServer(21078, null, null); tcpServer.start(); System.out.println("Start jt1078 server success!"); @@ -54,7 +54,7 @@ public class JT1078ServerTest { j9102.setCloseType(0); j9102.setStreamType(0); - String s = jt1078Template.stopLive("18864197066", j9102, 6); + Object s = jt1078Template.stopLive("18864197066", j9102, 6); System.out.println(s); } @@ -72,7 +72,7 @@ public class JT1078ServerTest { j9201.setStartTime("230428134100"); j9201.setEndTime("230428134200"); - String s = jt1078Template.startBackLive("18864197066", j9201, 6); + Object s = jt1078Template.startBackLive("18864197066", j9201, 6); System.out.println(s); } @@ -84,7 +84,7 @@ public class JT1078ServerTest { j9202.setPlaybackSpeed(0); j9202.setPlaybackTime("230428134100"); - String s = jt1078Template.controlBackLive("18864197066", j9202, 6); + Object s = jt1078Template.controlBackLive("18864197066", j9202, 6); System.out.println(s); } @@ -97,7 +97,7 @@ public class JT1078ServerTest { j9205.setStreamType(0); j9205.setStorageType(0); - String s = jt1078Template.queryBackTime("18864197066", j9205, 6); + Object s = jt1078Template.queryBackTime("18864197066", j9205, 6); System.out.println(s); } } diff --git a/web/.eslintrc.js b/web/.eslintrc.js index 446d8febc..303d04911 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -31,7 +31,7 @@ module.exports = { "comma-dangle": ["warn", "never"], "space-in-parens": "warn", "comma-spacing": "warn", - "object-curly-spacing": "warn", + "object-curly-spacing": ["warn", "always"], "arrow-spacing": "warn", semi: ["warn", "never"], "no-multi-spaces": "warn", diff --git a/web/package.json b/web/package.json index dffd8cbfc..51122c1eb 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,7 @@ "element-ui": "^2.15.14", "js-cookie": "2.2.0", "moment": "^2.29.1", + "moment-duration-format": "^2.3.2", "normalize.css": "7.0.0", "nprogress": "0.2.0", "ol": "^6.14.1", @@ -36,7 +37,8 @@ "vue-contextmenujs": "^1.4.11", "vue-router": "3.0.6", "vue-ztree-2.0": "^1.0.4", - "vuex": "3.1.0" + "vuex": "3.1.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.4", diff --git a/web/public/static/file/设置电话本模板.xlsx b/web/public/static/file/设置电话本模板.xlsx new file mode 100644 index 000000000..a993210dc Binary files /dev/null and b/web/public/static/file/设置电话本模板.xlsx differ diff --git a/web/public/static/images/bg13.png b/web/public/static/images/bg13.png deleted file mode 100644 index f208854c7..000000000 Binary files a/web/public/static/images/bg13.png and /dev/null differ diff --git a/web/public/static/images/bg14.png b/web/public/static/images/bg14.png deleted file mode 100644 index cbe4042c5..000000000 Binary files a/web/public/static/images/bg14.png and /dev/null differ diff --git a/web/public/static/images/bg17.png b/web/public/static/images/bg17.png deleted file mode 100644 index c2aa3344a..000000000 Binary files a/web/public/static/images/bg17.png and /dev/null differ diff --git a/web/public/static/images/bg18.png b/web/public/static/images/bg18.png deleted file mode 100644 index 78688afdd..000000000 Binary files a/web/public/static/images/bg18.png and /dev/null differ diff --git a/web/public/static/images/bg19.webp b/web/public/static/images/bg19.webp new file mode 100644 index 000000000..b106c5950 Binary files /dev/null and b/web/public/static/images/bg19.webp differ diff --git a/web/public/static/js/jessibuca/decoder.js b/web/public/static/js/jessibuca/decoder.js index 7813b9b20..a302084b2 100644 --- a/web/public/static/js/jessibuca/decoder.js +++ b/web/public/static/js/jessibuca/decoder.js @@ -1 +1 @@ -!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(require("path"),require("fs"),require("crypto")):"function"==typeof define&&define.amd?define(["path","fs","crypto"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).path,e.fs,e.crypto$1)}(this,(function(e,r,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),i=n(r),a=n(t);function s(e,r){return e(r={exports:{}},r.exports),r.exports}var l=s((function(e){var r=void 0!==r?r:{},t=(r={print:function(e){console.log("Jessibuca: [worker]:",e)},printErr:function(e){console.warn("Jessibuca: [worker]:",e),postMessage({cmd:"wasmError",message:e})}},Object.assign({},r)),n="./this.program",s="object"==typeof window,l="function"==typeof importScripts,u="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,c=!s&&!u&&!l;if(r.ENVIRONMENT)throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");var d,f,p,m,h,g,v="";if(u){if("object"!=typeof process)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");v=l?o.default.dirname(v)+"/":__dirname+"/",g=()=>{h||(m=i.default,h=o.default)},d=function(e,r){return g(),e=h.normalize(e),m.readFileSync(e,r?void 0:"utf8")},p=e=>{var r=d(e,!0);return r.buffer||(r=new Uint8Array(r)),F(r.buffer),r},f=(e,r,t)=>{g(),e=h.normalize(e),m.readFile(e,(function(e,n){e?t(e):r(n.buffer)}))},process.argv.length>1&&(n=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),e.exports=r,process.on("uncaughtException",(function(e){if(!(e instanceof St))throw e})),process.on("unhandledRejection",(function(e){throw e})),r.inspect=function(){return"[Emscripten Module object]"}}else if(c){if("object"==typeof process||"object"==typeof window||"function"==typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");"undefined"!=typeof read&&(d=function(e){return read(e)}),p=function(e){let r;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(r=read(e,"binary"),F("object"==typeof r),r)},f=function(e,r,t){setTimeout((()=>r(p(e))),0)},"undefined"!=typeof scriptArgs&&scriptArgs,"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print)}else{if(!s&&!l)throw new Error("environment detection error");if(l?v=self.location.href:"undefined"!=typeof document&&document.currentScript&&(v=document.currentScript.src),v=0!==v.indexOf("blob:")?v.substr(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1):"","object"!=typeof window&&"function"!=typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");d=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},l&&(p=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),f=(e,r,t)=>{var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=()=>{200==n.status||0==n.status&&n.response?r(n.response):t()},n.onerror=t,n.send(null)}}var y,E,w,b=r.print||console.log.bind(console),_=r.printErr||console.warn.bind(console);function T(e){T.shown||(T.shown={}),T.shown[e]||(T.shown[e]=1,_(e))}function k(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge("Module."+e+" has been replaced with plain "+t+" (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}})}function S(e,r){var t="'"+e+"' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)";return r&&(t+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"),t}function C(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge(S(e,t))}})}function P(e,t){Object.getOwnPropertyDescriptor(r,e)||(r[e]=()=>ge(S(e,t)))}Object.assign(r,t),t=null,y="fetchSettings",Object.getOwnPropertyDescriptor(r,y)&&ge("`Module."+y+"` was supplied but `"+y+"` not included in INCOMING_MODULE_JS_API"),r.arguments,k("arguments","arguments_"),r.thisProgram&&(n=r.thisProgram),k("thisProgram","thisProgram"),r.quit,k("quit","quit_"),F(void 0===r.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.read,"Module.read option was removed (modify read_ in JS)"),F(void 0===r.readAsync,"Module.readAsync option was removed (modify readAsync in JS)"),F(void 0===r.readBinary,"Module.readBinary option was removed (modify readBinary in JS)"),F(void 0===r.setWindowTitle,"Module.setWindowTitle option was removed (modify setWindowTitle in JS)"),F(void 0===r.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY"),k("read","read_"),k("readAsync","readAsync"),k("readBinary","readBinary"),k("setWindowTitle","setWindowTitle"),F(!c,"shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."),r.wasmBinary&&(E=r.wasmBinary),k("wasmBinary","wasmBinary"),r.noExitRuntime,k("noExitRuntime","noExitRuntime"),"object"!=typeof WebAssembly&&ge("no native wasm support detected");var A=!1;function F(e,r){e||ge("Assertion failed"+(r?": "+r:""))}var D="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function O(e,r,t){for(var n=r+t,o=r;e[o]&&!(o>=n);)++o;if(o-r>16&&e.buffer&&D)return D.decode(e.subarray(r,o));for(var i="";r>10,56320|1023&u)}}else i+=String.fromCharCode((31&a)<<6|s)}else i+=String.fromCharCode(a)}return i}function R(e,r){return e?O(U,e,r):""}function M(e,r,t,n){if(!(n>0))return 0;for(var o=t,i=t+n-1,a=0;a=55296&&s<=57343)s=65536+((1023&s)<<10)|1023&e.charCodeAt(++a);if(s<=127){if(t>=i)break;r[t++]=s}else if(s<=2047){if(t+1>=i)break;r[t++]=192|s>>6,r[t++]=128|63&s}else if(s<=65535){if(t+2>=i)break;r[t++]=224|s>>12,r[t++]=128|s>>6&63,r[t++]=128|63&s}else{if(t+3>=i)break;s>1114111&&T("Invalid Unicode code point 0x"+s.toString(16)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF)."),r[t++]=240|s>>18,r[t++]=128|s>>12&63,r[t++]=128|s>>6&63,r[t++]=128|63&s}}return r[t]=0,t-o}function N(e,r,t){return F("number"==typeof t,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),M(e,U,r,t)}function I(e){for(var r=0,t=0;t=55296&&n<=57343&&(n=65536+((1023&n)<<10)|1023&e.charCodeAt(++t)),n<=127?++r:r+=n<=2047?2:n<=65535?3:4}return r}var L,x,U,B,j,$,W,z,H,G="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0;function V(e,r){F(e%2==0,"Pointer passed to UTF16ToString must be aligned to two bytes!");for(var t=e,n=t>>1,o=n+r/2;!(n>=o)&&j[n];)++n;if((t=n<<1)-e>32&&G)return G.decode(U.subarray(e,t));for(var i="",a=0;!(a>=r/2);++a){var s=B[e+2*a>>1];if(0==s)break;i+=String.fromCharCode(s)}return i}function Y(e,r,t){if(F(r%2==0,"Pointer passed to stringToUTF16 must be aligned to two bytes!"),F("number"==typeof t,"stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<2)return 0;for(var n=r,o=(t-=2)<2*e.length?t/2:e.length,i=0;i>1]=a,r+=2}return B[r>>1]=0,r-n}function q(e){return 2*e.length}function X(e,r){F(e%4==0,"Pointer passed to UTF32ToString must be aligned to four bytes!");for(var t=0,n="";!(t>=r/4);){var o=$[e+4*t>>2];if(0==o)break;if(++t,o>=65536){var i=o-65536;n+=String.fromCharCode(55296|i>>10,56320|1023&i)}else n+=String.fromCharCode(o)}return n}function K(e,r,t){if(F(r%4==0,"Pointer passed to stringToUTF32 must be aligned to four bytes!"),F("number"==typeof t,"stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<4)return 0;for(var n=r,o=n+t-4,i=0;i=55296&&a<=57343)a=65536+((1023&a)<<10)|1023&e.charCodeAt(++i);if($[r>>2]=a,(r+=4)+4>o)break}return $[r>>2]=0,r-n}function J(e){for(var r=0,t=0;t=55296&&n<=57343&&++t,r+=4}return r}function Q(e){var r=I(e)+1,t=gt(r);return t&&M(e,x,t,r),t}function Z(e){L=e,r.HEAP8=x=new Int8Array(e),r.HEAP16=B=new Int16Array(e),r.HEAP32=$=new Int32Array(e),r.HEAPU8=U=new Uint8Array(e),r.HEAPU16=j=new Uint16Array(e),r.HEAPU32=W=new Uint32Array(e),r.HEAPF32=z=new Float32Array(e),r.HEAPF64=H=new Float64Array(e)}var ee=5242880;r.TOTAL_STACK&&F(ee===r.TOTAL_STACK,"the stack size can no longer be determined at runtime");var re,te=r.INITIAL_MEMORY||67108864;function ne(){var e=kt();F(0==(3&e)),$[e>>2]=34821223,$[e+4>>2]=2310721022,$[0]=1668509029}function oe(){if(!A){var e=kt(),r=W[e>>2],t=W[e+4>>2];34821223==r&&2310721022==t||ge("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x"+t.toString(16)+" 0x"+r.toString(16)),1668509029!==$[0]&&ge("Runtime error: The application has corrupted its heap memory area (address zero)!")}}k("INITIAL_MEMORY","INITIAL_MEMORY"),F(te>=ee,"INITIAL_MEMORY should be larger than TOTAL_STACK, was "+te+"! (TOTAL_STACK="+"5242880)"),F("undefined"!=typeof Int32Array&&"undefined"!=typeof Float64Array&&null!=Int32Array.prototype.subarray&&null!=Int32Array.prototype.set,"JS engine does not provide full typed array support"),F(!r.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally"),F(67108864==te,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically"),function(){var e=new Int16Array(1),r=new Int8Array(e.buffer);if(e[0]=25459,115!==r[0]||99!==r[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)"}();var ie=[],ae=[],se=[],le=!1;F(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var ue=0,ce=null,de=null,fe={};function pe(e){for(var r=e;;){if(!fe[e])return e;e=r+Math.random()}}function me(e){ue++,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(F(!fe[e]),fe[e]=1,null===ce&&"undefined"!=typeof setInterval&&(ce=setInterval((function(){if(A)return clearInterval(ce),void(ce=null);var e=!1;for(var r in fe)e||(e=!0,_("still waiting on run dependencies:")),_("dependency: "+r);e&&_("(end of list)")}),1e4))):_("warning: run dependency added without ID")}function he(e){if(ue--,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(F(fe[e]),delete fe[e]):_("warning: run dependency removed without ID"),0==ue&&(null!==ce&&(clearInterval(ce),ce=null),de)){var t=de;de=null,t()}}function ge(e){throw r.onAbort&&r.onAbort(e),_(e="Aborted("+e+")"),A=!0,new WebAssembly.RuntimeError(e)}var ve,ye,Ee;function we(e){return e.startsWith("data:application/octet-stream;base64,")}function be(e){return e.startsWith("file://")}function _e(e,t){return function(){var n=e,o=t;return t||(o=r.asm),F(le,"native function `"+n+"` called before runtime initialization"),o[e]||F(o[e],"exported native function `"+n+"` not found"),o[e].apply(null,arguments)}}function Te(e){try{if(e==ve&&E)return new Uint8Array(E);if(p)return p(e);throw"both async and sync fetching of the wasm failed"}catch(e){ge(e)}}function ke(e){for(;e.length>0;){var t=e.shift();if("function"!=typeof t){var n=t.func;"number"==typeof n?void 0===t.arg?Ce(n)():Ce(n)(t.arg):n(void 0===t.arg?null:t.arg)}else t(r)}}function Se(e){return e.replace(/\b_Z[\w\d_]+/g,(function(e){var r,t=(r=e,T("warning: build with -sDEMANGLE_SUPPORT to link in libcxxabi demangling"),r);return e===t?e:t+" ["+e+"]"}))}function Ce(e){return re.get(e)}function Pe(){var e=new Error;if(!e.stack){try{throw new Error}catch(r){e=r}if(!e.stack)return"(no stack trace available)"}return e.stack.toString()}we(ve="decoder.wasm")||(ve=function(e){return r.locateFile?r.locateFile(e,v):v+e}(ve));var Ae={isAbs:e=>"/"===e.charAt(0),splitPath:e=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(e).slice(1),normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),t++):t&&(e.splice(n,1),t--)}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=Ae.isAbs(e),t="/"===e.substr(-1);return(e=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||r||(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=Ae.splitPath(e),t=r[0],n=r[1];return t||n?(n&&(n=n.substr(0,n.length-1)),t+n):"."},basename:e=>{if("/"===e)return"/";var r=(e=(e=Ae.normalize(e)).replace(/\/$/,"")).lastIndexOf("/");return-1===r?e:e.substr(r+1)},join:function(){var e=Array.prototype.slice.call(arguments,0);return Ae.normalize(e.join("/"))},join2:(e,r)=>Ae.normalize(e+"/"+r)};var Fe={resolve:function(){for(var e="",r=!1,t=arguments.length-1;t>=-1&&!r;t--){var n=t>=0?arguments[t]:Ie.cwd();if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");if(!n)return"";e=n+"/"+e,r=Ae.isAbs(n)}return(r?"/":"")+(e=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||"."},relative:(e,r)=>{function t(e){for(var r=0;r=0&&""===e[t];t--);return r>t?[]:e.slice(r,t-r+1)}e=Fe.resolve(e).substr(1),r=Fe.resolve(r).substr(1);for(var n=t(e.split("/")),o=t(r.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s0?t.slice(0,n).toString("utf-8"):null}else"undefined"!=typeof window&&"function"==typeof window.prompt?null!==(r=window.prompt("Input: "))&&(r+="\n"):"function"==typeof readline&&null!==(r=readline())&&(r+="\n");if(!r)return null;e.input=pt(r,!0)}return e.input.shift()},put_char:function(e,r){null===r||10===r?(b(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(b(O(e.output,0)),e.output=[])}},default_tty1_ops:{put_char:function(e,r){null===r||10===r?(_(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(_(O(e.output,0)),e.output=[])}}};function Oe(e){e=function(e,r){return F(r,"alignment argument is required"),Math.ceil(e/r)*r}(e,65536);var r=bt(65536,e);return r?(function(e,r){U.fill(0,e,e+r)}(r,e),r):0}var Re={ops_table:null,mount:function(e){return Re.createNode(null,"/",16895,0)},createNode:function(e,r,t,n){if(Ie.isBlkdev(t)||Ie.isFIFO(t))throw new Ie.ErrnoError(63);Re.ops_table||(Re.ops_table={dir:{node:{getattr:Re.node_ops.getattr,setattr:Re.node_ops.setattr,lookup:Re.node_ops.lookup,mknod:Re.node_ops.mknod,rename:Re.node_ops.rename,unlink:Re.node_ops.unlink,rmdir:Re.node_ops.rmdir,readdir:Re.node_ops.readdir,symlink:Re.node_ops.symlink},stream:{llseek:Re.stream_ops.llseek}},file:{node:{getattr:Re.node_ops.getattr,setattr:Re.node_ops.setattr},stream:{llseek:Re.stream_ops.llseek,read:Re.stream_ops.read,write:Re.stream_ops.write,allocate:Re.stream_ops.allocate,mmap:Re.stream_ops.mmap,msync:Re.stream_ops.msync}},link:{node:{getattr:Re.node_ops.getattr,setattr:Re.node_ops.setattr,readlink:Re.node_ops.readlink},stream:{}},chrdev:{node:{getattr:Re.node_ops.getattr,setattr:Re.node_ops.setattr},stream:Ie.chrdev_stream_ops}});var o=Ie.createNode(e,r,t,n);return Ie.isDir(o.mode)?(o.node_ops=Re.ops_table.dir.node,o.stream_ops=Re.ops_table.dir.stream,o.contents={}):Ie.isFile(o.mode)?(o.node_ops=Re.ops_table.file.node,o.stream_ops=Re.ops_table.file.stream,o.usedBytes=0,o.contents=null):Ie.isLink(o.mode)?(o.node_ops=Re.ops_table.link.node,o.stream_ops=Re.ops_table.link.stream):Ie.isChrdev(o.mode)&&(o.node_ops=Re.ops_table.chrdev.node,o.stream_ops=Re.ops_table.chrdev.stream),o.timestamp=Date.now(),e&&(e.contents[r]=o,e.timestamp=o.timestamp),o},getFileDataAsTypedArray:function(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage:function(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){r=Math.max(r,t*(t<1048576?2:1.125)>>>0),0!=t&&(r=Math.max(r,256));var n=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(n.subarray(0,e.usedBytes),0)}},resizeFileStorage:function(e,r){if(e.usedBytes!=r)if(0==r)e.contents=null,e.usedBytes=0;else{var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r}},node_ops:{getattr:function(e){var r={};return r.dev=Ie.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,Ie.isDir(e.mode)?r.size=4096:Ie.isFile(e.mode)?r.size=e.usedBytes:Ie.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr:function(e,r){void 0!==r.mode&&(e.mode=r.mode),void 0!==r.timestamp&&(e.timestamp=r.timestamp),void 0!==r.size&&Re.resizeFileStorage(e,r.size)},lookup:function(e,r){throw Ie.genericErrors[44]},mknod:function(e,r,t,n){return Re.createNode(e,r,t,n)},rename:function(e,r,t){if(Ie.isDir(e.mode)){var n;try{n=Ie.lookupNode(r,t)}catch(e){}if(n)for(var o in n.contents)throw new Ie.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp,e.parent=r},unlink:function(e,r){delete e.contents[r],e.timestamp=Date.now()},rmdir:function(e,r){var t=Ie.lookupNode(e,r);for(var n in t.contents)throw new Ie.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now()},readdir:function(e){var r=[".",".."];for(var t in e.contents)e.contents.hasOwnProperty(t)&&r.push(t);return r},symlink:function(e,r,t){var n=Re.createNode(e,r,41471,0);return n.link=t,n},readlink:function(e){if(!Ie.isLink(e.mode))throw new Ie.ErrnoError(28);return e.link}},stream_ops:{read:function(e,r,t,n,o){var i=e.node.contents;if(o>=e.node.usedBytes)return 0;var a=Math.min(e.node.usedBytes-o,n);if(F(a>=0),a>8&&i.subarray)r.set(i.subarray(o,o+a),t);else for(var s=0;s0||n+t1&&void 0!==arguments[1]?arguments[1]:{};if(!(e=Fe.resolve(Ie.cwd(),e)))return{path:"",node:null};var t={follow_mount:!0,recurse_count:0};if(r=Object.assign(t,r),r.recurse_count>8)throw new Ie.ErrnoError(32);for(var n=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!1),o=Ie.root,i="/",a=0;a40)throw new Ie.ErrnoError(32)}}return{path:i,node:o}},getPath:e=>{for(var r;;){if(Ie.isRoot(e)){var t=e.mount.mountpoint;return r?"/"!==t[t.length-1]?t+"/"+r:t+r:t}r=r?e.name+"/"+r:e.name,e=e.parent}},hashName:(e,r)=>{for(var t=0,n=0;n>>0)%Ie.nameTable.length},hashAddNode:e=>{var r=Ie.hashName(e.parent.id,e.name);e.name_next=Ie.nameTable[r],Ie.nameTable[r]=e},hashRemoveNode:e=>{var r=Ie.hashName(e.parent.id,e.name);if(Ie.nameTable[r]===e)Ie.nameTable[r]=e.name_next;else for(var t=Ie.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next}},lookupNode:(e,r)=>{var t=Ie.mayLookup(e);if(t)throw new Ie.ErrnoError(t,e);for(var n=Ie.hashName(e.id,r),o=Ie.nameTable[n];o;o=o.name_next){var i=o.name;if(o.parent.id===e.id&&i===r)return o}return Ie.lookup(e,r)},createNode:(e,r,t,n)=>{F("object"==typeof e);var o=new Ie.FSNode(e,r,t,n);return Ie.hashAddNode(o),o},destroyNode:e=>{Ie.hashRemoveNode(e)},isRoot:e=>e===e.parent,isMountpoint:e=>!!e.mounted,isFile:e=>32768==(61440&e),isDir:e=>16384==(61440&e),isLink:e=>40960==(61440&e),isChrdev:e=>8192==(61440&e),isBlkdev:e=>24576==(61440&e),isFIFO:e=>4096==(61440&e),isSocket:e=>49152==(49152&e),flagModes:{r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},modeStringToFlags:e=>{var r=Ie.flagModes[e];if(void 0===r)throw new Error("Unknown file open mode: "+e);return r},flagsToPermissionString:e=>{var r=["r","w","rw"][3&e];return 512&e&&(r+="w"),r},nodePermissions:(e,r)=>Ie.ignorePermissions||(!r.includes("r")||292&e.mode)&&(!r.includes("w")||146&e.mode)&&(!r.includes("x")||73&e.mode)?0:2,mayLookup:e=>{var r=Ie.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate:(e,r)=>{try{Ie.lookupNode(e,r);return 20}catch(e){}return Ie.nodePermissions(e,"wx")},mayDelete:(e,r,t)=>{var n;try{n=Ie.lookupNode(e,r)}catch(e){return e.errno}var o=Ie.nodePermissions(e,"wx");if(o)return o;if(t){if(!Ie.isDir(n.mode))return 54;if(Ie.isRoot(n)||Ie.getPath(n)===Ie.cwd())return 10}else if(Ie.isDir(n.mode))return 31;return 0},mayOpen:(e,r)=>e?Ie.isLink(e.mode)?32:Ie.isDir(e.mode)&&("r"!==Ie.flagsToPermissionString(r)||512&r)?31:Ie.nodePermissions(e,Ie.flagsToPermissionString(r)):44,MAX_OPEN_FDS:4096,nextfd:function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Ie.MAX_OPEN_FDS;for(var t=e;t<=r;t++)if(!Ie.streams[t])return t;throw new Ie.ErrnoError(33)},getStream:e=>Ie.streams[e],createStream:(e,r,t)=>{Ie.FSStream||(Ie.FSStream=function(){this.shared={}},Ie.FSStream.prototype={object:{get:function(){return this.node},set:function(e){this.node=e}},isRead:{get:function(){return 1!=(2097155&this.flags)}},isWrite:{get:function(){return 0!=(2097155&this.flags)}},isAppend:{get:function(){return 1024&this.flags}},flags:{get:function(){return this.shared.flags},set:function(e){this.shared.flags=e}},position:{get function(){return this.shared.position},set:function(e){this.shared.position=e}}}),e=Object.assign(new Ie.FSStream,e);var n=Ie.nextfd(r,t);return e.fd=n,Ie.streams[n]=e,e},closeStream:e=>{Ie.streams[e]=null},chrdev_stream_ops:{open:e=>{var r=Ie.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open&&e.stream_ops.open(e)},llseek:()=>{throw new Ie.ErrnoError(70)}},major:e=>e>>8,minor:e=>255&e,makedev:(e,r)=>e<<8|r,registerDevice:(e,r)=>{Ie.devices[e]={stream_ops:r}},getDevice:e=>Ie.devices[e],getMounts:e=>{for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push.apply(t,n.mounts)}return r},syncfs:(e,r)=>{"function"==typeof e&&(r=e,e=!1),Ie.syncFSRequests++,Ie.syncFSRequests>1&&_("warning: "+Ie.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work");var t=Ie.getMounts(Ie.root.mount),n=0;function o(e){return F(Ie.syncFSRequests>0),Ie.syncFSRequests--,r(e)}function i(e){if(e)return i.errored?void 0:(i.errored=!0,o(e));++n>=t.length&&o(null)}t.forEach((r=>{if(!r.type.syncfs)return i(null);r.type.syncfs(r,e,i)}))},mount:(e,r,t)=>{if("string"==typeof e)throw e;var n,o="/"===t,i=!t;if(o&&Ie.root)throw new Ie.ErrnoError(10);if(!o&&!i){var a=Ie.lookupPath(t,{follow_mount:!1});if(t=a.path,n=a.node,Ie.isMountpoint(n))throw new Ie.ErrnoError(10);if(!Ie.isDir(n.mode))throw new Ie.ErrnoError(54)}var s={type:e,opts:r,mountpoint:t,mounts:[]},l=e.mount(s);return l.mount=s,s.root=l,o?Ie.root=l:n&&(n.mounted=s,n.mount&&n.mount.mounts.push(s)),l},unmount:e=>{var r=Ie.lookupPath(e,{follow_mount:!1});if(!Ie.isMountpoint(r.node))throw new Ie.ErrnoError(28);var t=r.node,n=t.mounted,o=Ie.getMounts(n);Object.keys(Ie.nameTable).forEach((e=>{for(var r=Ie.nameTable[e];r;){var t=r.name_next;o.includes(r.mount)&&Ie.destroyNode(r),r=t}})),t.mounted=null;var i=t.mount.mounts.indexOf(n);F(-1!==i),t.mount.mounts.splice(i,1)},lookup:(e,r)=>e.node_ops.lookup(e,r),mknod:(e,r,t)=>{var n=Ie.lookupPath(e,{parent:!0}).node,o=Ae.basename(e);if(!o||"."===o||".."===o)throw new Ie.ErrnoError(28);var i=Ie.mayCreate(n,o);if(i)throw new Ie.ErrnoError(i);if(!n.node_ops.mknod)throw new Ie.ErrnoError(63);return n.node_ops.mknod(n,o,r,t)},create:(e,r)=>(r=void 0!==r?r:438,r&=4095,r|=32768,Ie.mknod(e,r,0)),mkdir:(e,r)=>(r=void 0!==r?r:511,r&=1023,r|=16384,Ie.mknod(e,r,0)),mkdirTree:(e,r)=>{for(var t=e.split("/"),n="",o=0;o(void 0===t&&(t=r,r=438),r|=8192,Ie.mknod(e,r,t)),symlink:(e,r)=>{if(!Fe.resolve(e))throw new Ie.ErrnoError(44);var t=Ie.lookupPath(r,{parent:!0}).node;if(!t)throw new Ie.ErrnoError(44);var n=Ae.basename(r),o=Ie.mayCreate(t,n);if(o)throw new Ie.ErrnoError(o);if(!t.node_ops.symlink)throw new Ie.ErrnoError(63);return t.node_ops.symlink(t,n,e)},rename:(e,r)=>{var t,n,o=Ae.dirname(e),i=Ae.dirname(r),a=Ae.basename(e),s=Ae.basename(r);if(t=Ie.lookupPath(e,{parent:!0}).node,n=Ie.lookupPath(r,{parent:!0}).node,!t||!n)throw new Ie.ErrnoError(44);if(t.mount!==n.mount)throw new Ie.ErrnoError(75);var l,u=Ie.lookupNode(t,a),c=Fe.relative(e,i);if("."!==c.charAt(0))throw new Ie.ErrnoError(28);if("."!==(c=Fe.relative(r,o)).charAt(0))throw new Ie.ErrnoError(55);try{l=Ie.lookupNode(n,s)}catch(e){}if(u!==l){var d=Ie.isDir(u.mode),f=Ie.mayDelete(t,a,d);if(f)throw new Ie.ErrnoError(f);if(f=l?Ie.mayDelete(n,s,d):Ie.mayCreate(n,s))throw new Ie.ErrnoError(f);if(!t.node_ops.rename)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(u)||l&&Ie.isMountpoint(l))throw new Ie.ErrnoError(10);if(n!==t&&(f=Ie.nodePermissions(t,"w")))throw new Ie.ErrnoError(f);Ie.hashRemoveNode(u);try{t.node_ops.rename(u,n,s)}catch(e){throw e}finally{Ie.hashAddNode(u)}}},rmdir:e=>{var r=Ie.lookupPath(e,{parent:!0}).node,t=Ae.basename(e),n=Ie.lookupNode(r,t),o=Ie.mayDelete(r,t,!0);if(o)throw new Ie.ErrnoError(o);if(!r.node_ops.rmdir)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(n))throw new Ie.ErrnoError(10);r.node_ops.rmdir(r,t),Ie.destroyNode(n)},readdir:e=>{var r=Ie.lookupPath(e,{follow:!0}).node;if(!r.node_ops.readdir)throw new Ie.ErrnoError(54);return r.node_ops.readdir(r)},unlink:e=>{var r=Ie.lookupPath(e,{parent:!0}).node;if(!r)throw new Ie.ErrnoError(44);var t=Ae.basename(e),n=Ie.lookupNode(r,t),o=Ie.mayDelete(r,t,!1);if(o)throw new Ie.ErrnoError(o);if(!r.node_ops.unlink)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(n))throw new Ie.ErrnoError(10);r.node_ops.unlink(r,t),Ie.destroyNode(n)},readlink:e=>{var r=Ie.lookupPath(e).node;if(!r)throw new Ie.ErrnoError(44);if(!r.node_ops.readlink)throw new Ie.ErrnoError(28);return Fe.resolve(Ie.getPath(r.parent),r.node_ops.readlink(r))},stat:(e,r)=>{var t=Ie.lookupPath(e,{follow:!r}).node;if(!t)throw new Ie.ErrnoError(44);if(!t.node_ops.getattr)throw new Ie.ErrnoError(63);return t.node_ops.getattr(t)},lstat:e=>Ie.stat(e,!0),chmod:(e,r,t)=>{var n;"string"==typeof e?n=Ie.lookupPath(e,{follow:!t}).node:n=e;if(!n.node_ops.setattr)throw new Ie.ErrnoError(63);n.node_ops.setattr(n,{mode:4095&r|-4096&n.mode,timestamp:Date.now()})},lchmod:(e,r)=>{Ie.chmod(e,r,!0)},fchmod:(e,r)=>{var t=Ie.getStream(e);if(!t)throw new Ie.ErrnoError(8);Ie.chmod(t.node,r)},chown:(e,r,t,n)=>{var o;"string"==typeof e?o=Ie.lookupPath(e,{follow:!n}).node:o=e;if(!o.node_ops.setattr)throw new Ie.ErrnoError(63);o.node_ops.setattr(o,{timestamp:Date.now()})},lchown:(e,r,t)=>{Ie.chown(e,r,t,!0)},fchown:(e,r,t)=>{var n=Ie.getStream(e);if(!n)throw new Ie.ErrnoError(8);Ie.chown(n.node,r,t)},truncate:(e,r)=>{if(r<0)throw new Ie.ErrnoError(28);var t;"string"==typeof e?t=Ie.lookupPath(e,{follow:!0}).node:t=e;if(!t.node_ops.setattr)throw new Ie.ErrnoError(63);if(Ie.isDir(t.mode))throw new Ie.ErrnoError(31);if(!Ie.isFile(t.mode))throw new Ie.ErrnoError(28);var n=Ie.nodePermissions(t,"w");if(n)throw new Ie.ErrnoError(n);t.node_ops.setattr(t,{size:r,timestamp:Date.now()})},ftruncate:(e,r)=>{var t=Ie.getStream(e);if(!t)throw new Ie.ErrnoError(8);if(0==(2097155&t.flags))throw new Ie.ErrnoError(28);Ie.truncate(t.node,r)},utime:(e,r,t)=>{var n=Ie.lookupPath(e,{follow:!0}).node;n.node_ops.setattr(n,{timestamp:Math.max(r,t)})},open:(e,t,n,o,i)=>{if(""===e)throw new Ie.ErrnoError(44);var a;if(n=void 0===n?438:n,n=64&(t="string"==typeof t?Ie.modeStringToFlags(t):t)?4095&n|32768:0,"object"==typeof e)a=e;else{e=Ae.normalize(e);try{a=Ie.lookupPath(e,{follow:!(131072&t)}).node}catch(e){}}var s=!1;if(64&t)if(a){if(128&t)throw new Ie.ErrnoError(20)}else a=Ie.mknod(e,n,0),s=!0;if(!a)throw new Ie.ErrnoError(44);if(Ie.isChrdev(a.mode)&&(t&=-513),65536&t&&!Ie.isDir(a.mode))throw new Ie.ErrnoError(54);if(!s){var l=Ie.mayOpen(a,t);if(l)throw new Ie.ErrnoError(l)}512&t&&Ie.truncate(a,0),t&=-131713;var u=Ie.createStream({node:a,path:Ie.getPath(a),flags:t,seekable:!0,position:0,stream_ops:a.stream_ops,ungotten:[],error:!1},o,i);return u.stream_ops.open&&u.stream_ops.open(u),!r.logReadFiles||1&t||(Ie.readFiles||(Ie.readFiles={}),e in Ie.readFiles||(Ie.readFiles[e]=1)),u},close:e=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e)}catch(e){throw e}finally{Ie.closeStream(e.fd)}e.fd=null},isClosed:e=>null===e.fd,llseek:(e,r,t)=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new Ie.ErrnoError(70);if(0!=t&&1!=t&&2!=t)throw new Ie.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read:(e,r,t,n,o)=>{if(n<0||o<0)throw new Ie.ErrnoError(28);if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(1==(2097155&e.flags))throw new Ie.ErrnoError(8);if(Ie.isDir(e.node.mode))throw new Ie.ErrnoError(31);if(!e.stream_ops.read)throw new Ie.ErrnoError(28);var i=void 0!==o;if(i){if(!e.seekable)throw new Ie.ErrnoError(70)}else o=e.position;var a=e.stream_ops.read(e,r,t,n,o);return i||(e.position+=a),a},write:(e,r,t,n,o,i)=>{if(n<0||o<0)throw new Ie.ErrnoError(28);if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(0==(2097155&e.flags))throw new Ie.ErrnoError(8);if(Ie.isDir(e.node.mode))throw new Ie.ErrnoError(31);if(!e.stream_ops.write)throw new Ie.ErrnoError(28);e.seekable&&1024&e.flags&&Ie.llseek(e,0,2);var a=void 0!==o;if(a){if(!e.seekable)throw new Ie.ErrnoError(70)}else o=e.position;var s=e.stream_ops.write(e,r,t,n,o,i);return a||(e.position+=s),s},allocate:(e,r,t)=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(r<0||t<=0)throw new Ie.ErrnoError(28);if(0==(2097155&e.flags))throw new Ie.ErrnoError(8);if(!Ie.isFile(e.node.mode)&&!Ie.isDir(e.node.mode))throw new Ie.ErrnoError(43);if(!e.stream_ops.allocate)throw new Ie.ErrnoError(138);e.stream_ops.allocate(e,r,t)},mmap:(e,r,t,n,o,i)=>{if(0!=(2&o)&&0==(2&i)&&2!=(2097155&e.flags))throw new Ie.ErrnoError(2);if(1==(2097155&e.flags))throw new Ie.ErrnoError(2);if(!e.stream_ops.mmap)throw new Ie.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,o,i)},msync:(e,r,t,n,o)=>e&&e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,o):0,munmap:e=>0,ioctl:(e,r,t)=>{if(!e.stream_ops.ioctl)throw new Ie.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile:function(e){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(r.flags=r.flags||0,r.encoding=r.encoding||"binary","utf8"!==r.encoding&&"binary"!==r.encoding)throw new Error('Invalid encoding type "'+r.encoding+'"');var t,n=Ie.open(e,r.flags),o=Ie.stat(e),i=o.size,a=new Uint8Array(i);return Ie.read(n,a,0,i,0),"utf8"===r.encoding?t=O(a,0):"binary"===r.encoding&&(t=a),Ie.close(n),t},writeFile:function(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};t.flags=t.flags||577;var n=Ie.open(e,t.flags,t.mode);if("string"==typeof r){var o=new Uint8Array(I(r)+1),i=M(r,o,0,o.length);Ie.write(n,o,0,i,void 0,t.canOwn)}else{if(!ArrayBuffer.isView(r))throw new Error("Unsupported data type");Ie.write(n,r,0,r.byteLength,void 0,t.canOwn)}Ie.close(n)},cwd:()=>Ie.currentPath,chdir:e=>{var r=Ie.lookupPath(e,{follow:!0});if(null===r.node)throw new Ie.ErrnoError(44);if(!Ie.isDir(r.node.mode))throw new Ie.ErrnoError(54);var t=Ie.nodePermissions(r.node,"x");if(t)throw new Ie.ErrnoError(t);Ie.currentPath=r.path},createDefaultDirectories:()=>{Ie.mkdir("/tmp"),Ie.mkdir("/home"),Ie.mkdir("/home/web_user")},createDefaultDevices:()=>{Ie.mkdir("/dev"),Ie.registerDevice(Ie.makedev(1,3),{read:()=>0,write:(e,r,t,n,o)=>n}),Ie.mkdev("/dev/null",Ie.makedev(1,3)),De.register(Ie.makedev(5,0),De.default_tty_ops),De.register(Ie.makedev(6,0),De.default_tty1_ops),Ie.mkdev("/dev/tty",Ie.makedev(5,0)),Ie.mkdev("/dev/tty1",Ie.makedev(6,0));var e=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var e=new Uint8Array(1);return function(){return crypto.getRandomValues(e),e[0]}}if(u)try{var r=a.default;return function(){return r.randomBytes(1)[0]}}catch(e){}return function(){ge("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };")}}();Ie.createDevice("/dev","random",e),Ie.createDevice("/dev","urandom",e),Ie.mkdir("/dev/shm"),Ie.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{Ie.mkdir("/proc");var e=Ie.mkdir("/proc/self");Ie.mkdir("/proc/self/fd"),Ie.mount({mount:()=>{var r=Ie.createNode(e,"fd",16895,73);return r.node_ops={lookup:(e,r)=>{var t=+r,n=Ie.getStream(t);if(!n)throw new Ie.ErrnoError(8);var o={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>n.path}};return o.parent=o,o}},r}},{},"/proc/self/fd")},createStandardStreams:()=>{r.stdin?Ie.createDevice("/dev","stdin",r.stdin):Ie.symlink("/dev/tty","/dev/stdin"),r.stdout?Ie.createDevice("/dev","stdout",null,r.stdout):Ie.symlink("/dev/tty","/dev/stdout"),r.stderr?Ie.createDevice("/dev","stderr",null,r.stderr):Ie.symlink("/dev/tty1","/dev/stderr");var e=Ie.open("/dev/stdin",0),t=Ie.open("/dev/stdout",1),n=Ie.open("/dev/stderr",1);F(0===e.fd,"invalid handle for stdin ("+e.fd+")"),F(1===t.fd,"invalid handle for stdout ("+t.fd+")"),F(2===n.fd,"invalid handle for stderr ("+n.fd+")")},ensureErrnoError:()=>{Ie.ErrnoError||(Ie.ErrnoError=function(e,r){this.node=r,this.setErrno=function(e){for(var r in this.errno=e,Ne)if(Ne[r]===e){this.code=r;break}},this.setErrno(e),this.message=Me[e],this.stack&&(Object.defineProperty(this,"stack",{value:(new Error).stack,writable:!0}),this.stack=Se(this.stack))},Ie.ErrnoError.prototype=new Error,Ie.ErrnoError.prototype.constructor=Ie.ErrnoError,[44].forEach((e=>{Ie.genericErrors[e]=new Ie.ErrnoError(e),Ie.genericErrors[e].stack=""})))},staticInit:()=>{Ie.ensureErrnoError(),Ie.nameTable=new Array(4096),Ie.mount(Re,{},"/"),Ie.createDefaultDirectories(),Ie.createDefaultDevices(),Ie.createSpecialDirectories(),Ie.filesystems={MEMFS:Re}},init:(e,t,n)=>{F(!Ie.init.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"),Ie.init.initialized=!0,Ie.ensureErrnoError(),r.stdin=e||r.stdin,r.stdout=t||r.stdout,r.stderr=n||r.stderr,Ie.createStandardStreams()},quit:()=>{Ie.init.initialized=!1,wt();for(var e=0;e{var t=0;return e&&(t|=365),r&&(t|=146),t},findObject:(e,r)=>{var t=Ie.analyzePath(e,r);return t.exists?t.object:null},analyzePath:(e,r)=>{try{e=(n=Ie.lookupPath(e,{follow:!r})).path}catch(e){}var t={isRoot:!1,exists:!1,error:0,name:null,path:null,object:null,parentExists:!1,parentPath:null,parentObject:null};try{var n=Ie.lookupPath(e,{parent:!0});t.parentExists=!0,t.parentPath=n.path,t.parentObject=n.node,t.name=Ae.basename(e),n=Ie.lookupPath(e,{follow:!r}),t.exists=!0,t.path=n.path,t.object=n.node,t.name=n.node.name,t.isRoot="/"===n.path}catch(e){t.error=e.errno}return t},createPath:(e,r,t,n)=>{e="string"==typeof e?e:Ie.getPath(e);for(var o=r.split("/").reverse();o.length;){var i=o.pop();if(i){var a=Ae.join2(e,i);try{Ie.mkdir(a)}catch(e){}e=a}}return a},createFile:(e,r,t,n,o)=>{var i=Ae.join2("string"==typeof e?e:Ie.getPath(e),r),a=Ie.getMode(n,o);return Ie.create(i,a)},createDataFile:(e,r,t,n,o,i)=>{var a=r;e&&(e="string"==typeof e?e:Ie.getPath(e),a=r?Ae.join2(e,r):e);var s=Ie.getMode(n,o),l=Ie.create(a,s);if(t){if("string"==typeof t){for(var u=new Array(t.length),c=0,d=t.length;c{var o=Ae.join2("string"==typeof e?e:Ie.getPath(e),r),i=Ie.getMode(!!t,!!n);Ie.createDevice.major||(Ie.createDevice.major=64);var a=Ie.makedev(Ie.createDevice.major++,0);return Ie.registerDevice(a,{open:e=>{e.seekable=!1},close:e=>{n&&n.buffer&&n.buffer.length&&n(10)},read:(e,r,n,o,i)=>{for(var a=0,s=0;s{for(var a=0;a{if(e.isDevice||e.isFolder||e.link||e.contents)return!0;if("undefined"!=typeof XMLHttpRequest)throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");if(!d)throw new Error("Cannot load without read() or XMLHttpRequest.");try{e.contents=pt(d(e.url),!0),e.usedBytes=e.contents.length}catch(e){throw new Ie.ErrnoError(29)}},createLazyFile:(e,r,t,n,o)=>{function i(){this.lengthKnown=!1,this.chunks=[]}if(i.prototype.get=function(e){if(!(e>this.length-1||e<0)){var r=e%this.chunkSize,t=e/this.chunkSize|0;return this.getter(t)[r]}},i.prototype.setDataGetter=function(e){this.getter=e},i.prototype.cacheLength=function(){var e=new XMLHttpRequest;if(e.open("HEAD",t,!1),e.send(null),!(e.status>=200&&e.status<300||304===e.status))throw new Error("Couldn't load "+t+". Status: "+e.status);var r,n=Number(e.getResponseHeader("Content-length")),o=(r=e.getResponseHeader("Accept-Ranges"))&&"bytes"===r,i=(r=e.getResponseHeader("Content-Encoding"))&&"gzip"===r,a=1048576;o||(a=n);var s=this;s.setDataGetter((e=>{var r=e*a,o=(e+1)*a-1;if(o=Math.min(o,n-1),void 0===s.chunks[e]&&(s.chunks[e]=((e,r)=>{if(e>r)throw new Error("invalid range ("+e+", "+r+") or no bytes requested!");if(r>n-1)throw new Error("only "+n+" bytes available! programmer error!");var o=new XMLHttpRequest;if(o.open("GET",t,!1),n!==a&&o.setRequestHeader("Range","bytes="+e+"-"+r),o.responseType="arraybuffer",o.overrideMimeType&&o.overrideMimeType("text/plain; charset=x-user-defined"),o.send(null),!(o.status>=200&&o.status<300||304===o.status))throw new Error("Couldn't load "+t+". Status: "+o.status);return void 0!==o.response?new Uint8Array(o.response||[]):pt(o.responseText||"",!0)})(r,o)),void 0===s.chunks[e])throw new Error("doXHR failed!");return s.chunks[e]})),!i&&n||(a=n=1,n=this.getter(0).length,a=n,b("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=n,this._chunkSize=a,this.lengthKnown=!0},"undefined"!=typeof XMLHttpRequest){if(!l)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var a=new i;Object.defineProperties(a,{length:{get:function(){return this.lengthKnown||this.cacheLength(),this._length}},chunkSize:{get:function(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}});var s={isDevice:!1,contents:a}}else s={isDevice:!1,url:t};var u=Ie.createFile(e,r,s,n,o);s.contents?u.contents=s.contents:s.url&&(u.contents=null,u.url=s.url),Object.defineProperties(u,{usedBytes:{get:function(){return this.contents.length}}});var c={};return Object.keys(u.stream_ops).forEach((e=>{var r=u.stream_ops[e];c[e]=function(){return Ie.forceLoadFile(u),r.apply(null,arguments)}})),c.read=(e,r,t,n,o)=>{Ie.forceLoadFile(u);var i=e.node.contents;if(o>=i.length)return 0;var a=Math.min(i.length-o,n);if(F(a>=0),i.slice)for(var s=0;s{var c=r?Fe.resolve(Ae.join2(e,r)):e,d=pe("cp "+c);function p(t){function f(t){u&&u(),s||Ie.createDataFile(e,r,t,n,o,l),i&&i(),he(d)}Browser.handledByPreloadPlugin(t,c,f,(()=>{a&&a(),he(d)}))||f(t)}me(d),"string"==typeof t?function(e,r,t,n){var o=n?"":pe("al "+e);f(e,(function(t){F(t,'Loading data file "'+e+'" failed (no arrayBuffer).'),r(new Uint8Array(t)),o&&he(o)}),(function(r){if(!t)throw'Loading data file "'+e+'" failed.';t()})),o&&me(o)}(t,(e=>p(e)),a):p(t)},indexedDB:()=>window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,DB_NAME:()=>"EM_FS_"+window.location.pathname,DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ie.indexedDB();try{var o=n.open(Ie.DB_NAME(),Ie.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=()=>{b("creating db"),o.result.createObjectStore(Ie.DB_STORE_NAME)},o.onsuccess=()=>{var n=o.result.transaction([Ie.DB_STORE_NAME],"readwrite"),i=n.objectStore(Ie.DB_STORE_NAME),a=0,s=0,l=e.length;function u(){0==s?r():t()}e.forEach((e=>{var r=i.put(Ie.analyzePath(e).object.contents,e);r.onsuccess=()=>{++a+s==l&&u()},r.onerror=()=>{s++,a+s==l&&u()}})),n.onerror=t},o.onerror=t},loadFilesFromDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ie.indexedDB();try{var o=n.open(Ie.DB_NAME(),Ie.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=t,o.onsuccess=()=>{var n=o.result;try{var i=n.transaction([Ie.DB_STORE_NAME],"readonly")}catch(e){return void t(e)}var a=i.objectStore(Ie.DB_STORE_NAME),s=0,l=0,u=e.length;function c(){0==l?r():t()}e.forEach((e=>{var r=a.get(e);r.onsuccess=()=>{Ie.analyzePath(e).exists&&Ie.unlink(e),Ie.createDataFile(Ae.dirname(e),Ae.basename(e),r.result,!0,!0,!0),++s+l==u&&c()},r.onerror=()=>{l++,s+l==u&&c()}})),i.onerror=t},o.onerror=t},absolutePath:()=>{ge("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder:()=>{ge("FS.createFolder has been removed; use FS.mkdir instead")},createLink:()=>{ge("FS.createLink has been removed; use FS.symlink instead")},joinPath:()=>{ge("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc:()=>{ge("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath:()=>{ge("FS.standardizePath has been removed; use PATH.normalize instead")}},Le={DEFAULT_POLLMASK:5,calculateAt:function(e,r,t){if(Ae.isAbs(r))return r;var n;if(-100===e)n=Ie.cwd();else{var o=Ie.getStream(e);if(!o)throw new Ie.ErrnoError(8);n=o.path}if(0==r.length){if(!t)throw new Ie.ErrnoError(44);return n}return Ae.join2(n,r)},doStat:function(e,r,t){try{var n=e(r)}catch(e){if(e&&e.node&&Ae.normalize(r)!==Ae.normalize(Ie.getPath(e.node)))return-54;throw e}return $[t>>2]=n.dev,$[t+4>>2]=0,$[t+8>>2]=n.ino,$[t+12>>2]=n.mode,$[t+16>>2]=n.nlink,$[t+20>>2]=n.uid,$[t+24>>2]=n.gid,$[t+28>>2]=n.rdev,$[t+32>>2]=0,Ee=[n.size>>>0,(ye=n.size,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+40>>2]=Ee[0],$[t+44>>2]=Ee[1],$[t+48>>2]=4096,$[t+52>>2]=n.blocks,$[t+56>>2]=n.atime.getTime()/1e3|0,$[t+60>>2]=0,$[t+64>>2]=n.mtime.getTime()/1e3|0,$[t+68>>2]=0,$[t+72>>2]=n.ctime.getTime()/1e3|0,$[t+76>>2]=0,Ee=[n.ino>>>0,(ye=n.ino,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+80>>2]=Ee[0],$[t+84>>2]=Ee[1],0},doMsync:function(e,r,t,n,o){var i=U.slice(e,e+t);Ie.msync(r,i,o,t,n)},doMknod:function(e,r,t){switch(61440&r){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}return Ie.mknod(e,r,t),0},doReadlink:function(e,r,t){if(t<=0)return-28;var n=Ie.readlink(e),o=Math.min(t,I(n)),i=x[r+o];return N(n,r,t+1),x[r+o]=i,o},doAccess:function(e,r){if(-8&r)return-28;var t=Ie.lookupPath(e,{follow:!0}).node;if(!t)return-44;var n="";return 4&r&&(n+="r"),2&r&&(n+="w"),1&r&&(n+="x"),n&&Ie.nodePermissions(t,n)?-2:0},doReadv:function(e,r,t,n){for(var o=0,i=0;i>2],s=$[r+4>>2];r+=8;var l=Ie.read(e,x,a,s,n);if(l<0)return-1;if(o+=l,l>2],s=$[r+4>>2];r+=8;var l=Ie.write(e,x,a,s,n);if(l<0)return-1;o+=l}return o},varargs:void 0,get:function(){return F(null!=Le.varargs),Le.varargs+=4,$[Le.varargs-4>>2]},getStr:function(e){return R(e)},getStreamFromFD:function(e){var r=Ie.getStream(e);if(!r)throw new Ie.ErrnoError(8);return r}};function xe(e){switch(e){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+e)}}var Ue=void 0;function Be(e){for(var r="",t=e;U[t];)r+=Ue[U[t++]];return r}var je={},$e={},We={};function ze(e){if(void 0===e)return"_unknown";var r=(e=e.replace(/[^a-zA-Z0-9_]/g,"$")).charCodeAt(0);return r>=48&&r<=57?"_"+e:e}function He(e,r){return e=ze(e),new Function("body","return function "+e+'() {\n "use strict"; return body.apply(this, arguments);\n};\n')(r)}function Ge(e,r){var t=He(r,(function(e){this.name=r,this.message=e;var t=new Error(e).stack;void 0!==t&&(this.stack=this.toString()+"\n"+t.replace(/^Error(:[^\n]*)?\n/,""))}));return t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message},t}var Ve=void 0;function Ye(e){throw new Ve(e)}var qe=void 0;function Xe(e){throw new qe(e)}function Ke(e,r,t){function n(r){var n=t(r);n.length!==e.length&&Xe("Mismatched type converter count");for(var o=0;o{$e.hasOwnProperty(e)?o[r]=$e[e]:(i.push(e),je.hasOwnProperty(e)||(je[e]=[]),je[e].push((()=>{o[r]=$e[e],++a===i.length&&n(o)})))})),0===i.length&&n(o)}function Je(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!("argPackAdvance"in r))throw new TypeError("registerType registeredInstance requires argPackAdvance");var n=r.name;if(e||Ye('type "'+n+'" must have a positive integer typeid pointer'),$e.hasOwnProperty(e)){if(t.ignoreDuplicateRegistrations)return;Ye("Cannot register type '"+n+"' twice")}if($e[e]=r,delete We[e],je.hasOwnProperty(e)){var o=je[e];delete je[e],o.forEach((e=>e()))}}function Qe(e){if(!(this instanceof wr))return!1;if(!(e instanceof wr))return!1;for(var r=this.$$.ptrType.registeredClass,t=this.$$.ptr,n=e.$$.ptrType.registeredClass,o=e.$$.ptr;r.baseClass;)t=r.upcast(t),r=r.baseClass;for(;n.baseClass;)o=n.upcast(o),n=n.baseClass;return r===n&&t===o}function Ze(e){Ye(e.$$.ptrType.registeredClass.name+" instance already deleted")}var er=!1;function rr(e){}function tr(e){e.count.value-=1,0===e.count.value&&function(e){e.smartPtr?e.smartPtrType.rawDestructor(e.smartPtr):e.ptrType.registeredClass.rawDestructor(e.ptr)}(e)}function nr(e,r,t){if(r===t)return e;if(void 0===t.baseClass)return null;var n=nr(e,r,t.baseClass);return null===n?null:t.downcast(n)}var or={};function ir(){return Object.keys(dr).length}function ar(){var e=[];for(var r in dr)dr.hasOwnProperty(r)&&e.push(dr[r]);return e}var sr=[];function lr(){for(;sr.length;){var e=sr.pop();e.$$.deleteScheduled=!1,e.delete()}}var ur=void 0;function cr(e){ur=e,sr.length&&ur&&ur(lr)}var dr={};function fr(e,r){return r=function(e,r){for(void 0===r&&Ye("ptr should not be undefined");e.baseClass;)r=e.upcast(r),e=e.baseClass;return r}(e,r),dr[r]}function pr(e,r){return r.ptrType&&r.ptr||Xe("makeClassHandle requires ptr and ptrType"),!!r.smartPtrType!==!!r.smartPtr&&Xe("Both smartPtrType and smartPtr must be specified"),r.count={value:1},hr(Object.create(e,{$$:{value:r}}))}function mr(e){var r=this.getPointee(e);if(!r)return this.destructor(e),null;var t=fr(this.registeredClass,r);if(void 0!==t){if(0===t.$$.count.value)return t.$$.ptr=r,t.$$.smartPtr=e,t.clone();var n=t.clone();return this.destructor(e),n}function o(){return this.isSmartPointer?pr(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:r,smartPtrType:this,smartPtr:e}):pr(this.registeredClass.instancePrototype,{ptrType:this,ptr:e})}var i,a=this.registeredClass.getActualType(r),s=or[a];if(!s)return o.call(this);i=this.isConst?s.constPointerType:s.pointerType;var l=nr(r,this.registeredClass,i.registeredClass);return null===l?o.call(this):this.isSmartPointer?pr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l,smartPtrType:this,smartPtr:e}):pr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l})}function hr(e){return"undefined"==typeof FinalizationRegistry?(hr=e=>e,e):(er=new FinalizationRegistry((e=>{console.warn(e.leakWarning.stack.replace(/^Error: /,"")),tr(e.$$)})),hr=e=>{var r=e.$$;if(!!r.smartPtr){var t={$$:r},n=r.ptrType.registeredClass;t.leakWarning=new Error("Embind found a leaked C++ instance "+n.name+" <0x"+r.ptr.toString(16)+">.\nWe'll free it automatically in this case, but this functionality is not reliable across various environments.\nMake sure to invoke .delete() manually once you're done with the instance instead.\nOriginally allocated"),"captureStackTrace"in Error&&Error.captureStackTrace(t.leakWarning,mr),er.register(e,t,e)}return e},rr=e=>er.unregister(e),hr(e))}function gr(){if(this.$$.ptr||Ze(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var e,r=hr(Object.create(Object.getPrototypeOf(this),{$$:{value:(e=this.$$,{count:e.count,deleteScheduled:e.deleteScheduled,preservePointerOnDelete:e.preservePointerOnDelete,ptr:e.ptr,ptrType:e.ptrType,smartPtr:e.smartPtr,smartPtrType:e.smartPtrType})}}));return r.$$.count.value+=1,r.$$.deleteScheduled=!1,r}function vr(){this.$$.ptr||Ze(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),rr(this),tr(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)}function yr(){return!this.$$.ptr}function Er(){return this.$$.ptr||Ze(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),sr.push(this),1===sr.length&&ur&&ur(lr),this.$$.deleteScheduled=!0,this}function wr(){}function br(e,r,t){if(void 0===e[r].overloadTable){var n=e[r];e[r]=function(){return e[r].overloadTable.hasOwnProperty(arguments.length)||Ye("Function '"+t+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+e[r].overloadTable+")!"),e[r].overloadTable[arguments.length].apply(this,arguments)},e[r].overloadTable=[],e[r].overloadTable[n.argCount]=n}}function _r(e,r,t,n,o,i,a,s){this.name=e,this.constructor=r,this.instancePrototype=t,this.rawDestructor=n,this.baseClass=o,this.getActualType=i,this.upcast=a,this.downcast=s,this.pureVirtualFunctions=[]}function Tr(e,r,t){for(;r!==t;)r.upcast||Ye("Expected null or instance of "+t.name+", got an instance of "+r.name),e=r.upcast(e),r=r.baseClass;return e}function kr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name);var t=r.$$.ptrType.registeredClass;return Tr(r.$$.ptr,t,this.registeredClass)}function Sr(e,r){var t;if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),this.isSmartPointer?(t=this.rawConstructor(),null!==e&&e.push(this.rawDestructor,t),t):0;r.$$||Ye('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),!this.isConst&&r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);var n=r.$$.ptrType.registeredClass;if(t=Tr(r.$$.ptr,n,this.registeredClass),this.isSmartPointer)switch(void 0===r.$$.smartPtr&&Ye("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:r.$$.smartPtrType===this?t=r.$$.smartPtr:Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);break;case 1:t=r.$$.smartPtr;break;case 2:if(r.$$.smartPtrType===this)t=r.$$.smartPtr;else{var o=r.clone();t=this.rawShare(t,Yr.toHandle((function(){o.delete()}))),null!==e&&e.push(this.rawDestructor,t)}break;default:Ye("Unsupporting sharing policy")}return t}function Cr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+qr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+r.$$.ptrType.name+" to parameter type "+this.name);var t=r.$$.ptrType.registeredClass;return Tr(r.$$.ptr,t,this.registeredClass)}function Pr(e){return this.fromWireType(W[e>>2])}function Ar(e){return this.rawGetPointee&&(e=this.rawGetPointee(e)),e}function Fr(e){this.rawDestructor&&this.rawDestructor(e)}function Dr(e){null!==e&&e.delete()}function Or(e,r,t,n,o,i,a,s,l,u,c){this.name=e,this.registeredClass=r,this.isReference=t,this.isConst=n,this.isSmartPointer=o,this.pointeeType=i,this.sharingPolicy=a,this.rawGetPointee=s,this.rawConstructor=l,this.rawShare=u,this.rawDestructor=c,o||void 0!==r.baseClass?this.toWireType=Sr:n?(this.toWireType=kr,this.destructorFunction=null):(this.toWireType=Cr,this.destructorFunction=null)}function Rr(e,t,n){return e.includes("j")?function(e,t,n){F("dynCall_"+e in r,"bad function pointer type - no table for sig '"+e+"'"),n&&n.length?F(n.length===e.substring(1).replace(/j/g,"--").length):F(1==e.length);var o=r["dynCall_"+e];return n&&n.length?o.apply(null,[t].concat(n)):o.call(null,t)}(e,t,n):(F(Ce(t),"missing table entry in dynCall: "+t),Ce(t).apply(null,n))}function Mr(e,r){var t=(e=Be(e)).includes("j")?function(e,r){F(e.includes("j"),"getDynCaller should only be called with i64 sigs");var t=[];return function(){return t.length=0,Object.assign(t,arguments),Rr(e,r,t)}}(e,r):Ce(r);return"function"!=typeof t&&Ye("unknown function pointer with signature "+e+": "+r),t}var Nr=void 0;function Ir(e){var r=Et(e),t=Be(r);return ht(r),t}function Lr(e,r){var t=[],n={};throw r.forEach((function e(r){n[r]||$e[r]||(We[r]?We[r].forEach(e):(t.push(r),n[r]=!0))})),new Nr(e+": "+t.map(Ir).join([", "]))}function xr(e,r){for(var t=[],n=0;n>2)+n]);return t}function Ur(e){for(;e.length;){var r=e.pop();e.pop()(r)}}function Br(e,r){if(!(e instanceof Function))throw new TypeError("new_ called with constructor type "+typeof e+" which is not a function");var t=He(e.name||"unknownFunctionName",(function(){}));t.prototype=e.prototype;var n=new t,o=e.apply(n,r);return o instanceof Object?o:n}function jr(e,r,t,n,o){var i=r.length;i<2&&Ye("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var a=null!==r[1]&&null!==t,s=!1,l=1;l0?", ":"")+d),f+=(u?"var rv = ":"")+"invoker(fn"+(d.length>0?", ":"")+d+");\n",s)f+="runDestructors(destructors);\n";else for(l=a?1:2;l4&&0==--zr[e].refcount&&(zr[e]=void 0,Wr.push(e))}function Gr(){for(var e=0,r=5;r(e||Ye("Cannot use deleted val. handle = "+e),zr[e].value),toHandle:e=>{switch(e){case void 0:return 1;case null:return 2;case!0:return 3;case!1:return 4;default:var r=Wr.length?Wr.pop():zr.length;return zr[r]={refcount:1,value:e},r}}};function qr(e){if(null===e)return"null";var r=typeof e;return"object"===r||"array"===r||"function"===r?e.toString():""+e}function Xr(e,r){switch(r){case 2:return function(e){return this.fromWireType(z[e>>2])};case 3:return function(e){return this.fromWireType(H[e>>3])};default:throw new TypeError("Unknown float type: "+e)}}function Kr(e,r,t){switch(r){case 0:return t?function(e){return x[e]}:function(e){return U[e]};case 1:return t?function(e){return B[e>>1]}:function(e){return j[e>>1]};case 2:return t?function(e){return $[e>>2]}:function(e){return W[e>>2]};default:throw new TypeError("Unknown integer type: "+e)}}function Jr(e,r){var t=$e[e];return void 0===t&&Ye(r+" has unknown type "+Ir(e)),t}var Qr={};var Zr=[];var et=[];function rt(e,r){return F(r===(0|r)),(e>>>0)+4294967296*r}function tt(e,r){if(e<=0)return e;var t=r<=32?Math.abs(1<=t&&(r<=32||e>t)&&(e=-2*t+e),e}function nt(e,r){return e>=0?e:r<=32?2*Math.abs(1<>3]),n+=8):"i64"==e?(r=[$[n>>2],$[n+4>>2]],n+=8):(F(0==(3&n)),e="i32",r=$[n>>2],n+=4),r}for(var i,a,s,l,u,c,d=[];;){var f=t;if(0===(i=x[t>>0]))break;if(a=x[t+1>>0],37==i){var p=!1,m=!1,h=!1,g=!1,v=!1;e:for(;;){switch(a){case 43:p=!0;break;case 45:m=!0;break;case 35:h=!0;break;case 48:if(g)break e;g=!0;break;case 32:v=!0;break;default:break e}t++,a=x[t+1>>0]}var y=0;if(42==a)y=o("i32"),t++,a=x[t+1>>0];else for(;a>=48&&a<=57;)y=10*y+(a-48),t++,a=x[t+1>>0];var E,w=!1,b=-1;if(46==a){if(b=0,w=!0,t++,42==(a=x[t+1>>0]))b=o("i32"),t++;else for(;;){var _=x[t+1>>0];if(_<48||_>57)break;b=10*b+(_-48),t++}a=x[t+1>>0]}switch(b<0&&(b=6,w=!1),String.fromCharCode(a)){case"h":104==x[t+2>>0]?(t++,E=1):E=2;break;case"l":108==x[t+2>>0]?(t++,E=8):E=4;break;case"L":case"q":case"j":E=8;break;case"z":case"t":case"I":E=4;break;default:E=null}switch(E&&t++,a=x[t+1>>0],String.fromCharCode(a)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":var T=100==a||105==a;if(s=o("i"+8*(E=E||4)),8==E&&(s=117==a?(u=s[0],c=s[1],(u>>>0)+4294967296*(c>>>0)):rt(s[0],s[1])),E<=4)s=(T?tt:nt)(s&Math.pow(256,E)-1,8*E);var k=Math.abs(s),S="";if(100==a||105==a)A=tt(s,8*E).toString(10);else if(117==a)A=nt(s,8*E).toString(10),s=Math.abs(s);else if(111==a)A=(h?"0":"")+k.toString(8);else if(120==a||88==a){if(S=h&&0!=s?"0x":"",s<0){s=-s,A=(k-1).toString(16);for(var C=[],P=0;P=0&&(p?S="+"+S:v&&(S=" "+S)),"-"==A.charAt(0)&&(S="-"+S,A=A.substr(1));S.length+A.lengthR&&R>=-4?(a=(103==a?"f":"F").charCodeAt(0),b-=R+1):(a=(103==a?"e":"E").charCodeAt(0),b--),O=Math.min(b,20)}101==a||69==a?(A=s.toExponential(O),/[eE][-+]\d$/.test(A)&&(A=A.slice(0,-1)+"0"+A.slice(-1))):102!=a&&70!=a||(A=s.toFixed(O),0===s&&((l=s)<0||0===l&&1/l==-1/0)&&(A="-"+A));var M=A.split("e");if(D&&!h)for(;M[0].length>1&&M[0].includes(".")&&("0"==M[0].slice(-1)||"."==M[0].slice(-1));)M[0]=M[0].slice(0,-1);else for(h&&-1==A.indexOf(".")&&(M[0]+=".");b>O++;)M[0]+="0";A=M[0]+(M.length>1?"e"+M[1]:""),69==a&&(A=A.toUpperCase()),s>=0&&(p?A="+"+A:v&&(A=" "+A))}else A=(s<0?"-":"")+"inf",g=!1;for(;A.length>0]);else d=d.concat(pt("(null)".substr(0,I),!0));if(m)for(;I0;)d.push(32);m||d.push(o("i8"));break;case"n":var L=o("i32*");$[L>>2]=d.length;break;case"%":d.push(i);break;default:for(P=f;P>0])}t+=2}else d.push(i),t+=1}return d}function it(e){if(!e||!e.callee||!e.callee.name)return[null,"",""];e.callee.toString();var r=e.callee.name,t="(",n=!0;for(var o in e){var i=e[o];n||(t+=", "),n=!1,t+="number"==typeof i||"string"==typeof i?i:"("+typeof i+")"}t+=")";var a=e.callee.caller;return n&&(t=""),[e=a?a.arguments:[],r,t]}function at(e,r){24&e&&(r=r.replace(/\s+$/,""),r+=(r.length>0?"\n":"")+function(e){var r=Pe(),t=r.lastIndexOf("_emscripten_log"),n=r.lastIndexOf("_emscripten_get_callstack"),o=r.indexOf("\n",Math.max(t,n))+1;r=r.slice(o),32&e&&T("EM_LOG_DEMANGLE is deprecated; ignoring"),8&e&&"undefined"==typeof emscripten_source_map&&(T('Source map information is not available, emscripten_log with EM_LOG_C_STACK will be ignored. Build with "--pre-js $EMSCRIPTEN/src/emscripten-source-map.min.js" linker flag to add source map loading to code.'),e^=8,e|=16);var i=null;if(128&e)for(i=it(arguments);i[1].includes("_emscripten_");)i=it(i[0]);var a=r.split("\n");r="";var s=new RegExp("\\s*(.*?)@(.*?):([0-9]+):([0-9]+)"),l=new RegExp("\\s*(.*?)@(.*):(.*)(:(.*))?"),u=new RegExp("\\s*at (.*?) \\((.*):(.*):(.*)\\)");for(var c in a){var d=a[c],f="",p="",m=0,h=0,g=u.exec(d);if(g&&5==g.length)f=g[1],p=g[2],m=g[3],h=g[4];else{if((g=s.exec(d))||(g=l.exec(d)),!(g&&g.length>=4)){r+=d+"\n";continue}f=g[1],p=g[2],m=g[3],h=0|g[4]}var v=!1;if(8&e){var y=emscripten_source_map.originalPositionFor({line:m,column:h});(v=y&&y.source)&&(64&e&&(y.source=y.source.substring(y.source.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=" at "+f+" ("+y.source+":"+y.line+":"+y.column+")\n")}(16&e||!v)&&(64&e&&(p=p.substring(p.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=(v?" = "+f:" at "+f)+" ("+p+":"+m+":"+h+")\n"),128&e&&i[0]&&(i[1]==f&&i[2].length>0&&(r=r.replace(/\s+$/,""),r+=" with values: "+i[1]+i[2]+"\n"),i=it(i[0]))}return r.replace(/\s+$/,"")}(e)),1&e?4&e?console.error(r):2&e?console.warn(r):512&e?console.info(r):256&e?console.debug(r):console.log(r):6&e?_(r):b(r)}function st(e){try{return w.grow(e-L.byteLength+65535>>>16),Z(w.buffer),1}catch(r){_("emscripten_realloc_buffer: Attempted to grow heap from "+L.byteLength+" bytes to "+e+" bytes, but got error: "+r)}}var lt={};function ut(){if(!ut.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:n||"./this.program"};for(var r in lt)void 0===lt[r]?delete e[r]:e[r]=lt[r];var t=[];for(var r in e)t.push(r+"="+e[r]);ut.strings=t}return ut.strings}var ct=function(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=Ie.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n},dt=365,ft=146;function pt(e,r,t){var n=t>0?t:I(e)+1,o=new Array(n),i=M(e,o,0,o.length);return r&&(o.length=i),o}Object.defineProperties(ct.prototype,{read:{get:function(){return(this.mode&dt)===dt},set:function(e){e?this.mode|=dt:this.mode&=-366}},write:{get:function(){return(this.mode&ft)===ft},set:function(e){e?this.mode|=ft:this.mode&=-147}},isFolder:{get:function(){return Ie.isDir(this.mode)}},isDevice:{get:function(){return Ie.isChrdev(this.mode)}}}),Ie.FSNode=ct,Ie.staticInit(),Ne={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135},function(){for(var e=new Array(256),r=0;r<256;++r)e[r]=String.fromCharCode(r);Ue=e}(),Ve=r.BindingError=Ge(Error,"BindingError"),qe=r.InternalError=Ge(Error,"InternalError"),wr.prototype.isAliasOf=Qe,wr.prototype.clone=gr,wr.prototype.delete=vr,wr.prototype.isDeleted=yr,wr.prototype.deleteLater=Er,r.getInheritedInstanceCount=ir,r.getLiveInheritedInstances=ar,r.flushPendingDeletes=lr,r.setDelayFunction=cr,Or.prototype.getPointee=Ar,Or.prototype.destructor=Fr,Or.prototype.argPackAdvance=8,Or.prototype.readValueFromPointer=Pr,Or.prototype.deleteObject=Dr,Or.prototype.fromWireType=mr,Nr=r.UnboundTypeError=Ge(Error,"UnboundTypeError"),r.count_emval_handles=Gr,r.get_first_emval=Vr;var mt={__syscall_fcntl64:function(e,r,t){Le.varargs=t;try{var n=Le.getStreamFromFD(e);switch(r){case 0:return(o=Le.get())<0?-28:Ie.createStream(n,o).fd;case 1:case 2:case 6:case 7:return 0;case 3:return n.flags;case 4:var o=Le.get();return n.flags|=o,0;case 5:o=Le.get();return B[o+0>>1]=2,0;case 16:case 8:default:return-28;case 9:return i=28,$[yt()>>2]=i,-1}}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return-e.errno}var i},__syscall_openat:function(e,r,t,n){Le.varargs=n;try{r=Le.getStr(r),r=Le.calculateAt(e,r);var o=n?Le.get():0;return Ie.open(r,t,o).fd}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return-e.errno}},_embind_register_bigint:function(e,r,t,n,o){},_embind_register_bool:function(e,r,t,n,o){var i=xe(t);Je(e,{name:r=Be(r),fromWireType:function(e){return!!e},toWireType:function(e,r){return r?n:o},argPackAdvance:8,readValueFromPointer:function(e){var n;if(1===t)n=x;else if(2===t)n=B;else{if(4!==t)throw new TypeError("Unknown boolean type size: "+r);n=$}return this.fromWireType(n[e>>i])},destructorFunction:null})},_embind_register_class:function(e,t,n,o,i,a,s,l,u,c,d,f,p){d=Be(d),a=Mr(i,a),l&&(l=Mr(s,l)),c&&(c=Mr(u,c)),p=Mr(f,p);var m=ze(d);!function(e,t,n){r.hasOwnProperty(e)?((void 0===n||void 0!==r[e].overloadTable&&void 0!==r[e].overloadTable[n])&&Ye("Cannot register public name '"+e+"' twice"),br(r,e,e),r.hasOwnProperty(n)&&Ye("Cannot register multiple overloads of a function with the same number of arguments ("+n+")!"),r[e].overloadTable[n]=t):(r[e]=t,void 0!==n&&(r[e].numArguments=n))}(m,(function(){Lr("Cannot construct "+d+" due to unbound types",[o])})),Ke([e,t,n],o?[o]:[],(function(t){var n,i;t=t[0],i=o?(n=t.registeredClass).instancePrototype:wr.prototype;var s=He(m,(function(){if(Object.getPrototypeOf(this)!==u)throw new Ve("Use 'new' to construct "+d);if(void 0===f.constructor_body)throw new Ve(d+" has no accessible constructor");var e=f.constructor_body[arguments.length];if(void 0===e)throw new Ve("Tried to invoke ctor of "+d+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(f.constructor_body).toString()+") parameters instead!");return e.apply(this,arguments)})),u=Object.create(i,{constructor:{value:s}});s.prototype=u;var f=new _r(d,s,u,p,n,a,l,c),h=new Or(d,f,!0,!1,!1),g=new Or(d+"*",f,!1,!1,!1),v=new Or(d+" const*",f,!1,!0,!1);return or[e]={pointerType:g,constPointerType:v},function(e,t,n){r.hasOwnProperty(e)||Xe("Replacing nonexistant public symbol"),void 0!==r[e].overloadTable&&void 0!==n?r[e].overloadTable[n]=t:(r[e]=t,r[e].argCount=n)}(m,s),[h,g,v]}))},_embind_register_class_constructor:function(e,r,t,n,o,i){F(r>0);var a=xr(r,t);o=Mr(n,o),Ke([],[e],(function(e){var t="constructor "+(e=e[0]).name;if(void 0===e.registeredClass.constructor_body&&(e.registeredClass.constructor_body=[]),void 0!==e.registeredClass.constructor_body[r-1])throw new Ve("Cannot register multiple constructors with identical number of parameters ("+(r-1)+") for class '"+e.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!");return e.registeredClass.constructor_body[r-1]=()=>{Lr("Cannot construct "+e.name+" due to unbound types",a)},Ke([],a,(function(n){return n.splice(1,0,null),e.registeredClass.constructor_body[r-1]=jr(t,n,null,o,i),[]})),[]}))},_embind_register_class_function:function(e,r,t,n,o,i,a,s){var l=xr(t,n);r=Be(r),i=Mr(o,i),Ke([],[e],(function(e){var n=(e=e[0]).name+"."+r;function o(){Lr("Cannot call "+n+" due to unbound types",l)}r.startsWith("@@")&&(r=Symbol[r.substring(2)]),s&&e.registeredClass.pureVirtualFunctions.push(r);var u=e.registeredClass.instancePrototype,c=u[r];return void 0===c||void 0===c.overloadTable&&c.className!==e.name&&c.argCount===t-2?(o.argCount=t-2,o.className=e.name,u[r]=o):(br(u,r,n),u[r].overloadTable[t-2]=o),Ke([],l,(function(o){var s=jr(n,o,e,i,a);return void 0===u[r].overloadTable?(s.argCount=t-2,u[r]=s):u[r].overloadTable[t-2]=s,[]})),[]}))},_embind_register_class_property:function(e,r,t,n,o,i,a,s,l,u){r=Be(r),o=Mr(n,o),Ke([],[e],(function(e){var n=(e=e[0]).name+"."+r,c={get:function(){Lr("Cannot access "+n+" due to unbound types",[t,a])},enumerable:!0,configurable:!0};return c.set=l?()=>{Lr("Cannot access "+n+" due to unbound types",[t,a])}:e=>{Ye(n+" is a read-only property")},Object.defineProperty(e.registeredClass.instancePrototype,r,c),Ke([],l?[t,a]:[t],(function(t){var a=t[0],c={get:function(){var r=$r(this,e,n+" getter");return a.fromWireType(o(i,r))},enumerable:!0};if(l){l=Mr(s,l);var d=t[1];c.set=function(r){var t=$r(this,e,n+" setter"),o=[];l(u,t,d.toWireType(o,r)),Ur(o)}}return Object.defineProperty(e.registeredClass.instancePrototype,r,c),[]})),[]}))},_embind_register_emval:function(e,r){Je(e,{name:r=Be(r),fromWireType:function(e){var r=Yr.toValue(e);return Hr(e),r},toWireType:function(e,r){return Yr.toHandle(r)},argPackAdvance:8,readValueFromPointer:Pr,destructorFunction:null})},_embind_register_float:function(e,r,t){var n=xe(t);Je(e,{name:r=Be(r),fromWireType:function(e){return e},toWireType:function(e,r){if("number"!=typeof r&&"boolean"!=typeof r)throw new TypeError('Cannot convert "'+qr(r)+'" to '+this.name);return r},argPackAdvance:8,readValueFromPointer:Xr(r,n),destructorFunction:null})},_embind_register_integer:function(e,r,t,n,o){r=Be(r),-1===o&&(o=4294967295);var i=xe(t),a=e=>e;if(0===n){var s=32-8*t;a=e=>e<>>s}var l=r.includes("unsigned"),u=(e,t)=>{if("number"!=typeof e&&"boolean"!=typeof e)throw new TypeError('Cannot convert "'+qr(e)+'" to '+t);if(eo)throw new TypeError('Passing a number "'+qr(e)+'" from JS side to C/C++ side to an argument of type "'+r+'", which is outside the valid range ['+n+", "+o+"]!")};Je(e,{name:r,fromWireType:a,toWireType:l?function(e,r){return u(r,this.name),r>>>0}:function(e,r){return u(r,this.name),r},argPackAdvance:8,readValueFromPointer:Kr(r,i,0!==n),destructorFunction:null})},_embind_register_memory_view:function(e,r,t){var n=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][r];function o(e){var r=W,t=r[e>>=2],o=r[e+1];return new n(L,o,t)}Je(e,{name:t=Be(t),fromWireType:o,argPackAdvance:8,readValueFromPointer:o},{ignoreDuplicateRegistrations:!0})},_embind_register_std_string:function(e,r){var t="std::string"===(r=Be(r));Je(e,{name:r,fromWireType:function(e){var r,n=W[e>>2];if(t)for(var o=e+4,i=0;i<=n;++i){var a=e+4+i;if(i==n||0==U[a]){var s=R(o,a-o);void 0===r?r=s:(r+=String.fromCharCode(0),r+=s),o=a+1}}else{var l=new Array(n);for(i=0;iI(r):()=>r.length)(),i=gt(4+o+1);if(W[i>>2]=o,t&&n)N(r,i+4,o+1);else if(n)for(var a=0;a255&&(ht(i),Ye("String has UTF-16 code units that do not fit in 8 bits")),U[i+4+a]=s}else for(a=0;aj,s=1):4===r&&(n=X,o=K,a=J,i=()=>W,s=2),Je(e,{name:t,fromWireType:function(e){for(var t,o=W[e>>2],a=i(),l=e+4,u=0;u<=o;++u){var c=e+4+u*r;if(u==o||0==a[c>>s]){var d=n(l,c-l);void 0===t?t=d:(t+=String.fromCharCode(0),t+=d),l=c+r}}return ht(e),t},toWireType:function(e,n){"string"!=typeof n&&Ye("Cannot pass non-string to C++ string type "+t);var i=a(n),l=gt(4+i+r);return W[l>>2]=i>>s,o(n,l+4,i+r),null!==e&&e.push(ht,l),l},argPackAdvance:8,readValueFromPointer:Pr,destructorFunction:function(e){ht(e)}})},_embind_register_void:function(e,r){Je(e,{isVoid:!0,name:r=Be(r),argPackAdvance:0,fromWireType:function(){},toWireType:function(e,r){}})},_emscripten_date_now:function(){return Date.now()},_emval_as:function(e,r,t){e=Yr.toValue(e),r=Jr(r,"emval::as");var n=[],o=Yr.toHandle(n);return $[t>>2]=o,r.toWireType(n,e)},_emval_call_void_method:function(e,r,t,n){var o,i;(e=Zr[e])(r=Yr.toValue(r),t=void 0===(i=Qr[o=t])?Be(o):i,null,n)},_emval_decref:Hr,_emval_get_method_caller:function(e,r){var t=function(e,r){for(var t=new Array(e),n=0;n>2)+n],"parameter "+n);return t}(e,r),n=t[0],o=n.name+"_$"+t.slice(1).map((function(e){return e.name})).join("_")+"$",i=et[o];if(void 0!==i)return i;for(var a=["retType"],s=[n],l="",u=0;u4&&(zr[e].refcount+=1)},_emval_run_destructors:function(e){Ur(Yr.toValue(e)),Hr(e)},_emval_take_value:function(e,r){var t=(e=Jr(e,"_emval_take_value")).readValueFromPointer(r);return Yr.toHandle(t)},_gmtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getUTCSeconds(),$[r+4>>2]=t.getUTCMinutes(),$[r+8>>2]=t.getUTCHours(),$[r+12>>2]=t.getUTCDate(),$[r+16>>2]=t.getUTCMonth(),$[r+20>>2]=t.getUTCFullYear()-1900,$[r+24>>2]=t.getUTCDay();var n=Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0),o=(t.getTime()-n)/864e5|0;$[r+28>>2]=o},_localtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getSeconds(),$[r+4>>2]=t.getMinutes(),$[r+8>>2]=t.getHours(),$[r+12>>2]=t.getDate(),$[r+16>>2]=t.getMonth(),$[r+20>>2]=t.getFullYear()-1900,$[r+24>>2]=t.getDay();var n=new Date(t.getFullYear(),0,1),o=(t.getTime()-n.getTime())/864e5|0;$[r+28>>2]=o,$[r+36>>2]=-60*t.getTimezoneOffset();var i=new Date(t.getFullYear(),6,1).getTimezoneOffset(),a=n.getTimezoneOffset(),s=0|(i!=a&&t.getTimezoneOffset()==Math.min(a,i));$[r+32>>2]=s},_mktime_js:function(e){var r=new Date($[e+20>>2]+1900,$[e+16>>2],$[e+12>>2],$[e+8>>2],$[e+4>>2],$[e>>2],0),t=$[e+32>>2],n=r.getTimezoneOffset(),o=new Date(r.getFullYear(),0,1),i=new Date(r.getFullYear(),6,1).getTimezoneOffset(),a=o.getTimezoneOffset(),s=Math.min(a,i);if(t<0)$[e+32>>2]=Number(i!=a&&s==n);else if(t>0!=(s==n)){var l=Math.max(a,i),u=t>0?s:l;r.setTime(r.getTime()+6e4*(u-n))}$[e+24>>2]=r.getDay();var c=(r.getTime()-o.getTime())/864e5|0;return $[e+28>>2]=c,$[e>>2]=r.getSeconds(),$[e+4>>2]=r.getMinutes(),$[e+8>>2]=r.getHours(),$[e+12>>2]=r.getDate(),$[e+16>>2]=r.getMonth(),r.getTime()/1e3|0},_tzset_js:function e(r,t,n){e.called||(e.called=!0,function(e,r,t){var n=(new Date).getFullYear(),o=new Date(n,0,1),i=new Date(n,6,1),a=o.getTimezoneOffset(),s=i.getTimezoneOffset(),l=Math.max(a,s);function u(e){var r=e.toTimeString().match(/\(([A-Za-z ]+)\)$/);return r?r[1]:"GMT"}$[e>>2]=60*l,$[r>>2]=Number(a!=s);var c=u(o),d=u(i),f=Q(c),p=Q(d);s>2]=f,$[t+4>>2]=p):($[t>>2]=p,$[t+4>>2]=f)}(r,t,n))},abort:function(){ge("native code called abort()")},emscripten_log:function(e,r,t){at(e,O(ot(r,t),0))},emscripten_resize_heap:function(e){var r=U.length;F((e>>>=0)>r);var t,n,o=2147483648;if(e>o)return _("Cannot enlarge memory, asked to go up to "+e+" bytes, but the limit is "+"2147483648 bytes!"),!1;for(var i=1;i<=4;i*=2){var a=r*(1+.2/i);a=Math.min(a,e+100663296);var s=Math.min(o,(t=Math.max(e,a))+((n=65536)-t%n)%n);if(st(s))return!0}return _("Failed to grow the heap from "+r+" bytes to "+s+" bytes, not enough memory!"),!1},environ_get:function(e,r){var t=0;return ut().forEach((function(n,o){var i=r+t;$[e+4*o>>2]=i,function(e,r,t){for(var n=0;n>0]=e.charCodeAt(n);t||(x[r>>0]=0)}(n,i),t+=n.length+1})),0},environ_sizes_get:function(e,r){var t=ut();$[e>>2]=t.length;var n=0;return t.forEach((function(e){n+=e.length+1})),$[r>>2]=n,0},fd_close:function(e){try{var r=Le.getStreamFromFD(e);return Ie.close(r),0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_fdstat_get:function(e,r){try{var t=Le.getStreamFromFD(e),n=t.tty?2:Ie.isDir(t.mode)?3:Ie.isLink(t.mode)?7:4;return x[r>>0]=n,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_read:function(e,r,t,n){try{var o=Le.getStreamFromFD(e),i=Le.doReadv(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_seek:function(e,r,t,n,o){try{var i=Le.getStreamFromFD(e),a=4294967296*t+(r>>>0),s=9007199254740992;return a<=-s||a>=s?-61:(Ie.llseek(i,a,n),Ee=[i.position>>>0,(ye=i.position,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[o>>2]=Ee[0],$[o+4>>2]=Ee[1],i.getdents&&0===a&&0===n&&(i.getdents=null),0)}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_write:function(e,r,t,n){try{var o=Le.getStreamFromFD(e),i=Le.doWritev(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},setTempRet0:function(e){}};!function(){var e={env:mt,wasi_snapshot_preview1:mt};function t(e,t){var n,o=e.exports;r.asm=o,F(w=r.asm.memory,"memory not found in wasm exports"),Z(w.buffer),F(re=r.asm.__indirect_function_table,"table not found in wasm exports"),n=r.asm.__wasm_call_ctors,ae.unshift(n),he("wasm-instantiate")}me("wasm-instantiate");var n=r;function o(e){F(r===n,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"),n=null,t(e.instance)}function i(r){return function(){if(!E&&(s||l)){if("function"==typeof fetch&&!be(ve))return fetch(ve,{credentials:"same-origin"}).then((function(e){if(!e.ok)throw"failed to load wasm binary file at '"+ve+"'";return e.arrayBuffer()})).catch((function(){return Te(ve)}));if(f)return new Promise((function(e,r){f(ve,(function(r){e(new Uint8Array(r))}),r)}))}return Promise.resolve().then((function(){return Te(ve)}))}().then((function(r){return WebAssembly.instantiate(r,e)})).then((function(e){return e})).then(r,(function(e){_("failed to asynchronously prepare wasm: "+e),be(ve)&&_("warning: Loading from a file URI ("+ve+") is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing"),ge(e)}))}if(r.instantiateWasm)try{return r.instantiateWasm(e,t)}catch(e){return _("Module.instantiateWasm callback failed with error: "+e),!1}E||"function"!=typeof WebAssembly.instantiateStreaming||we(ve)||be(ve)||"function"!=typeof fetch?i(o):fetch(ve,{credentials:"same-origin"}).then((function(r){return WebAssembly.instantiateStreaming(r,e).then(o,(function(e){return _("wasm streaming compile failed: "+e),_("falling back to ArrayBuffer instantiation"),i(o)}))}))}(),r.___wasm_call_ctors=_e("__wasm_call_ctors");var ht=r._free=_e("free"),gt=r._malloc=_e("malloc"),vt=r._strlen=_e("strlen"),yt=r.___errno_location=_e("__errno_location"),Et=r.___getTypeName=_e("__getTypeName");r.___embind_register_native_and_builtin_types=_e("__embind_register_native_and_builtin_types");var wt=r.___stdio_exit=_e("__stdio_exit"),bt=r._emscripten_builtin_memalign=_e("emscripten_builtin_memalign"),_t=r._emscripten_stack_init=function(){return(_t=r._emscripten_stack_init=r.asm.emscripten_stack_init).apply(null,arguments)};r._emscripten_stack_get_free=function(){return(r._emscripten_stack_get_free=r.asm.emscripten_stack_get_free).apply(null,arguments)},r._emscripten_stack_get_base=function(){return(r._emscripten_stack_get_base=r.asm.emscripten_stack_get_base).apply(null,arguments)};var Tt,kt=r._emscripten_stack_get_end=function(){return(kt=r._emscripten_stack_get_end=r.asm.emscripten_stack_get_end).apply(null,arguments)};function St(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function Ct(e){function t(){Tt||(Tt=!0,r.calledRun=!0,A||(oe(),F(!le),le=!0,r.noFSInit||Ie.init.initialized||Ie.init(),Ie.ignorePermissions=!1,ke(ae),r.onRuntimeInitialized&&r.onRuntimeInitialized(),F(!r._main,'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'),function(){if(oe(),r.postRun)for("function"==typeof r.postRun&&(r.postRun=[r.postRun]);r.postRun.length;)e=r.postRun.shift(),se.unshift(e);var e;ke(se)}()))}ue>0||(_t(),ne(),function(){if(r.preRun)for("function"==typeof r.preRun&&(r.preRun=[r.preRun]);r.preRun.length;)e=r.preRun.shift(),ie.unshift(e);var e;ke(ie)}(),ue>0||(r.setStatus?(r.setStatus("Running..."),setTimeout((function(){setTimeout((function(){r.setStatus("")}),1),t()}),1)):t(),oe()))}if(r.stackSave=_e("stackSave"),r.stackRestore=_e("stackRestore"),r.stackAlloc=_e("stackAlloc"),r.dynCall_ijiii=_e("dynCall_ijiii"),r.dynCall_viiijj=_e("dynCall_viiijj"),r.dynCall_jij=_e("dynCall_jij"),r.dynCall_jii=_e("dynCall_jii"),r.dynCall_jiji=_e("dynCall_jiji"),r._ff_h264_cabac_tables=112940,P("intArrayFromString",!1),P("intArrayToString",!1),P("ccall",!1),P("cwrap",!1),P("setValue",!1),P("getValue",!1),P("allocate",!1),P("UTF8ArrayToString",!1),P("UTF8ToString",!1),P("stringToUTF8Array",!1),P("stringToUTF8",!1),P("lengthBytesUTF8",!1),P("stackTrace",!1),P("addOnPreRun",!1),P("addOnInit",!1),P("addOnPreMain",!1),P("addOnExit",!1),P("addOnPostRun",!1),P("writeStringToMemory",!1),P("writeArrayToMemory",!1),P("writeAsciiToMemory",!1),P("addRunDependency",!0),P("removeRunDependency",!0),P("FS_createFolder",!1),P("FS_createPath",!0),P("FS_createDataFile",!0),P("FS_createPreloadedFile",!0),P("FS_createLazyFile",!0),P("FS_createLink",!1),P("FS_createDevice",!0),P("FS_unlink",!0),P("getLEB",!1),P("getFunctionTables",!1),P("alignFunctionTables",!1),P("registerFunctions",!1),P("addFunction",!1),P("removeFunction",!1),P("prettyPrint",!1),P("dynCall",!1),P("getCompilerSetting",!1),P("print",!1),P("printErr",!1),P("getTempRet0",!1),P("setTempRet0",!1),P("callMain",!1),P("abort",!1),P("keepRuntimeAlive",!1),P("ptrToString",!1),P("zeroMemory",!1),P("stringToNewUTF8",!1),P("emscripten_realloc_buffer",!1),P("ENV",!1),P("ERRNO_CODES",!1),P("ERRNO_MESSAGES",!1),P("setErrNo",!1),P("inetPton4",!1),P("inetNtop4",!1),P("inetPton6",!1),P("inetNtop6",!1),P("readSockaddr",!1),P("writeSockaddr",!1),P("DNS",!1),P("getHostByName",!1),P("Protocols",!1),P("Sockets",!1),P("getRandomDevice",!1),P("traverseStack",!1),P("UNWIND_CACHE",!1),P("convertPCtoSourceLocation",!1),P("readAsmConstArgsArray",!1),P("readAsmConstArgs",!1),P("mainThreadEM_ASM",!1),P("jstoi_q",!1),P("jstoi_s",!1),P("getExecutableName",!1),P("listenOnce",!1),P("autoResumeAudioContext",!1),P("dynCallLegacy",!1),P("getDynCaller",!1),P("dynCall",!1),P("setWasmTableEntry",!1),P("getWasmTableEntry",!1),P("handleException",!1),P("runtimeKeepalivePush",!1),P("runtimeKeepalivePop",!1),P("callUserCallback",!1),P("maybeExit",!1),P("safeSetTimeout",!1),P("asmjsMangle",!1),P("asyncLoad",!1),P("alignMemory",!1),P("mmapAlloc",!1),P("reallyNegative",!1),P("unSign",!1),P("reSign",!1),P("formatString",!1),P("PATH",!1),P("PATH_FS",!1),P("SYSCALLS",!1),P("getSocketFromFD",!1),P("getSocketAddress",!1),P("JSEvents",!1),P("registerKeyEventCallback",!1),P("specialHTMLTargets",!1),P("maybeCStringToJsString",!1),P("findEventTarget",!1),P("findCanvasEventTarget",!1),P("getBoundingClientRect",!1),P("fillMouseEventData",!1),P("registerMouseEventCallback",!1),P("registerWheelEventCallback",!1),P("registerUiEventCallback",!1),P("registerFocusEventCallback",!1),P("fillDeviceOrientationEventData",!1),P("registerDeviceOrientationEventCallback",!1),P("fillDeviceMotionEventData",!1),P("registerDeviceMotionEventCallback",!1),P("screenOrientation",!1),P("fillOrientationChangeEventData",!1),P("registerOrientationChangeEventCallback",!1),P("fillFullscreenChangeEventData",!1),P("registerFullscreenChangeEventCallback",!1),P("registerRestoreOldStyle",!1),P("hideEverythingExceptGivenElement",!1),P("restoreHiddenElements",!1),P("setLetterbox",!1),P("currentFullscreenStrategy",!1),P("restoreOldWindowedStyle",!1),P("softFullscreenResizeWebGLRenderTarget",!1),P("doRequestFullscreen",!1),P("fillPointerlockChangeEventData",!1),P("registerPointerlockChangeEventCallback",!1),P("registerPointerlockErrorEventCallback",!1),P("requestPointerLock",!1),P("fillVisibilityChangeEventData",!1),P("registerVisibilityChangeEventCallback",!1),P("registerTouchEventCallback",!1),P("fillGamepadEventData",!1),P("registerGamepadEventCallback",!1),P("registerBeforeUnloadEventCallback",!1),P("fillBatteryEventData",!1),P("battery",!1),P("registerBatteryEventCallback",!1),P("setCanvasElementSize",!1),P("getCanvasElementSize",!1),P("demangle",!1),P("demangleAll",!1),P("jsStackTrace",!1),P("stackTrace",!1),P("getEnvStrings",!1),P("checkWasiClock",!1),P("writeI53ToI64",!1),P("writeI53ToI64Clamped",!1),P("writeI53ToI64Signaling",!1),P("writeI53ToU64Clamped",!1),P("writeI53ToU64Signaling",!1),P("readI53FromI64",!1),P("readI53FromU64",!1),P("convertI32PairToI53",!1),P("convertU32PairToI53",!1),P("dlopenMissingError",!1),P("setImmediateWrapped",!1),P("clearImmediateWrapped",!1),P("polyfillSetImmediate",!1),P("uncaughtExceptionCount",!1),P("exceptionLast",!1),P("exceptionCaught",!1),P("ExceptionInfo",!1),P("exception_addRef",!1),P("exception_decRef",!1),P("Browser",!1),P("setMainLoop",!1),P("wget",!1),P("FS",!1),P("MEMFS",!1),P("TTY",!1),P("PIPEFS",!1),P("SOCKFS",!1),P("_setNetworkCallback",!1),P("tempFixedLengthArray",!1),P("miniTempWebGLFloatBuffers",!1),P("heapObjectForWebGLType",!1),P("heapAccessShiftForWebGLHeap",!1),P("GL",!1),P("emscriptenWebGLGet",!1),P("computeUnpackAlignedImageSize",!1),P("emscriptenWebGLGetTexPixelData",!1),P("emscriptenWebGLGetUniform",!1),P("webglGetUniformLocation",!1),P("webglPrepareUniformLocationsBeforeFirstUse",!1),P("webglGetLeftBracePos",!1),P("emscriptenWebGLGetVertexAttrib",!1),P("writeGLArray",!1),P("AL",!1),P("SDL_unicode",!1),P("SDL_ttfContext",!1),P("SDL_audio",!1),P("SDL",!1),P("SDL_gfx",!1),P("GLUT",!1),P("EGL",!1),P("GLFW_Window",!1),P("GLFW",!1),P("GLEW",!1),P("IDBStore",!1),P("runAndAbortIfError",!1),P("InternalError",!1),P("BindingError",!1),P("UnboundTypeError",!1),P("PureVirtualError",!1),P("init_embind",!1),P("throwInternalError",!1),P("throwBindingError",!1),P("throwUnboundTypeError",!1),P("ensureOverloadTable",!1),P("exposePublicSymbol",!1),P("replacePublicSymbol",!1),P("extendError",!1),P("createNamedFunction",!1),P("registeredInstances",!1),P("getBasestPointer",!1),P("registerInheritedInstance",!1),P("unregisterInheritedInstance",!1),P("getInheritedInstance",!1),P("getInheritedInstanceCount",!1),P("getLiveInheritedInstances",!1),P("registeredTypes",!1),P("awaitingDependencies",!1),P("typeDependencies",!1),P("registeredPointers",!1),P("registerType",!1),P("whenDependentTypesAreResolved",!1),P("embind_charCodes",!1),P("embind_init_charCodes",!1),P("readLatin1String",!1),P("getTypeName",!1),P("heap32VectorToArray",!1),P("requireRegisteredType",!1),P("getShiftFromSize",!1),P("integerReadValueFromPointer",!1),P("enumReadValueFromPointer",!1),P("floatReadValueFromPointer",!1),P("simpleReadValueFromPointer",!1),P("runDestructors",!1),P("new_",!1),P("craftInvokerFunction",!1),P("embind__requireFunction",!1),P("tupleRegistrations",!1),P("structRegistrations",!1),P("genericPointerToWireType",!1),P("constNoSmartPtrRawPointerToWireType",!1),P("nonConstNoSmartPtrRawPointerToWireType",!1),P("init_RegisteredPointer",!1),P("RegisteredPointer",!1),P("RegisteredPointer_getPointee",!1),P("RegisteredPointer_destructor",!1),P("RegisteredPointer_deleteObject",!1),P("RegisteredPointer_fromWireType",!1),P("runDestructor",!1),P("releaseClassHandle",!1),P("finalizationRegistry",!1),P("detachFinalizer_deps",!1),P("detachFinalizer",!1),P("attachFinalizer",!1),P("makeClassHandle",!1),P("init_ClassHandle",!1),P("ClassHandle",!1),P("ClassHandle_isAliasOf",!1),P("throwInstanceAlreadyDeleted",!1),P("ClassHandle_clone",!1),P("ClassHandle_delete",!1),P("deletionQueue",!1),P("ClassHandle_isDeleted",!1),P("ClassHandle_deleteLater",!1),P("flushPendingDeletes",!1),P("delayFunction",!1),P("setDelayFunction",!1),P("RegisteredClass",!1),P("shallowCopyInternalPointer",!1),P("downcastPointer",!1),P("upcastPointer",!1),P("validateThis",!1),P("char_0",!1),P("char_9",!1),P("makeLegalFunctionName",!1),P("emval_handle_array",!1),P("emval_free_list",!1),P("emval_symbols",!1),P("init_emval",!1),P("count_emval_handles",!1),P("get_first_emval",!1),P("getStringOrSymbol",!1),P("Emval",!1),P("emval_newers",!1),P("craftEmvalAllocator",!1),P("emval_get_global",!1),P("emval_methodCallers",!1),P("emval_registeredMethods",!1),P("warnOnce",!1),P("stackSave",!1),P("stackRestore",!1),P("stackAlloc",!1),P("AsciiToString",!1),P("stringToAscii",!1),P("UTF16ToString",!1),P("stringToUTF16",!1),P("lengthBytesUTF16",!1),P("UTF32ToString",!1),P("stringToUTF32",!1),P("lengthBytesUTF32",!1),P("allocateUTF8",!1),P("allocateUTF8OnStack",!1),r.writeStackCookie=ne,r.checkStackCookie=oe,C("ALLOC_NORMAL",!1),C("ALLOC_STACK",!1),de=function e(){Tt||Ct(),Tt||(de=e)},r.run=Ct,r.preInit)for("function"==typeof r.preInit&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();Ct(),e.exports=r}));const u=1e3,c=1e3,d=!1,f=!1,p=!1,m=!1,h="initVideo",g="render",v="playAudio",y="initAudio",E="audioCode",w="videoCode",b=1,_=2,T="init",k="decode",S="audioDecode",C="videoDecode",P="close",A="updateConfig",F="key",D="delta";s((function(e){!function(){var r="undefined"!=typeof window&&void 0!==window.document?window.document:{},t=e.exports,n=function(){for(var e,t=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],n=0,o=t.length,i={};n{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){}})(),Date.now||(Date.now=function(){return(new Date).getTime()}),l.postRun=function(){var e=[],r=[],t={};"VideoEncoder"in self&&(t={hasInit:!1,isEmitInfo:!1,offscreenCanvas:null,offscreenCanvasCtx:null,decoder:new VideoDecoder({output:function(e){t.isEmitInfo||(n.opt.debug&&console.log("Jessibuca: [worker] Webcodecs Video Decoder initSize"),postMessage({cmd:h,w:e.codedWidth,h:e.codedHeight}),t.isEmitInfo=!0,t.offscreenCanvas=new OffscreenCanvas(e.codedWidth,e.codedHeight),t.offscreenCanvasCtx=t.offscreenCanvas.getContext("2d")),t.offscreenCanvasCtx.drawImage(e,0,0,e.codedWidth,e.codedHeight);let r=t.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:r,delay:n.delay,ts:0},[r]),setTimeout((function(){e.close?e.close():e.destroy()}),100)},error:function(e){console.error(e)}}),decode:function(e,r){const o=e[0]>>4==1;if(t.hasInit){const n=new EncodedVideoChunk({data:e.slice(5),timestamp:r,type:o?F:D});t.decoder.decode(n)}else if(o&&0===e[1]){const r=15&e[0];n.setVideoCodec(r);const o=function(e){let r=e.subarray(1,4),t="avc1.";for(let e=0;e<3;e++){let n=r[e].toString(16);n.length<2&&(n="0"+n),t+=n}return{codec:t,description:e}}(e.slice(5));t.decoder.configure(o),t.hasInit=!0}},reset(){t.hasInit=!1,t.isEmitInfo=!1,t.offscreenCanvas=null,t.offscreenCanvasCtx=null}});var n={opt:{debug:d,useOffscreen:p,useWCS:f,videoBuffer:u,openWebglAlignment:m,videoBufferDelay:c},useOffscreen:function(){return n.opt.useOffscreen&&"undefined"!=typeof OffscreenCanvas},initAudioPlanar:function(e,t){postMessage({cmd:y,sampleRate:t,channels:e});var n=[],o=0;this.playAudioPlanar=function(t,i,a){for(var s=i,u=[],c=0,d=0;d<2;d++){var f=l.HEAPU32[(t>>2)+d]>>2;u[d]=l.HEAPF32.subarray(f,f+s)}if(o){if(!(s>=(i=1024-o)))return o+=s,r[0]=Float32Array.of(...r[0],...u[0]),void(2==e&&(r[1]=Float32Array.of(...r[1],...u[1])));n[0]=Float32Array.of(...r[0],...u[0].subarray(0,i)),2==e&&(n[1]=Float32Array.of(...r[1],...u[1].subarray(0,i))),postMessage({cmd:v,buffer:n,ts:a},n.map((e=>e.buffer))),c=i,s-=i}for(o=s;o>=1024;o-=1024)n[0]=u[0].slice(c,c+=1024),2==e&&(n[1]=u[1].slice(c-1024,c)),postMessage({cmd:v,buffer:n,ts:a},n.map((e=>e.buffer)));o&&(r[0]=u[0].slice(c),2==e&&(r[1]=u[1].slice(c)))}},setVideoCodec:function(e){postMessage({cmd:w,code:e})},setAudioCodec:function(e){postMessage({cmd:E,code:e})},setVideoSize:function(e,r){postMessage({cmd:h,w:e,h:r});var t=e*r,o=t>>2;n.useOffscreen()?(this.offscreenCanvas=new OffscreenCanvas(e,r),this.offscreenCanvasGL=this.offscreenCanvas.getContext("webgl"),this.webglObj=((e,r)=>{var t=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),n=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");r&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var o=e.createShader(e.VERTEX_SHADER);e.shaderSource(o,t),e.compileShader(o),e.getShaderParameter(o,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(o));var i=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(i,n),e.compileShader(i),e.getShaderParameter(i,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(i));var a=e.createProgram();e.attachShader(a,o),e.attachShader(a,i),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var s=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,s),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var l=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(l),e.vertexAttribPointer(l,2,e.FLOAT,!1,0,0);var u=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,u),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function d(r,t){var n=e.createTexture();return e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,r),t),n}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var f=d("ySampler",0),p=d("uSampler",1),m=d("vSampler",2);return{render:function(r,t,n,o,i){e.viewport(0,0,r,t),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,f),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r,t,0,e.LUMINANCE,e.UNSIGNED_BYTE,n),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,m),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,i),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(s),e.deleteBuffer(u),e.deleteTexture(f),e.deleteTexture(p),e.deleteTexture(m)}catch(e){}}}})(this.offscreenCanvasGL,n.opt.openWebglAlignment),this.draw=function(n,i,a,s){const u=l.HEAPU8.subarray(i,i+t),c=l.HEAPU8.subarray(a,a+o),d=l.HEAPU8.subarray(s,s+o);this.webglObj.render(e,r,u,c,d);let f=this.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:f,delay:this.delay,ts:n},[f])}):this.draw=function(e,r,n,i){const a=[Uint8Array.from(l.HEAPU8.subarray(r,r+t)),Uint8Array.from(l.HEAPU8.subarray(n,n+o)),Uint8Array.from(l.HEAPU8.subarray(i,i+o))];postMessage({cmd:g,output:a,delay:this.delay,ts:e},a.map((e=>e.buffer)))}},getDelay:function(e){if(!e)return-1;if(this.firstTimestamp){if(e){const r=Date.now()-this.startTimestamp,t=e-this.firstTimestamp;this.delay=r>=t?r-t:t-r}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay},resetDelay:function(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1},init:function(){n.opt.debug&&console.log("Jessibuca: [worker] init");const r=e=>{n.opt.useWCS&&n.useOffscreen()&&e.type===_&&t.decode?t.decode(e.payload,e.ts):e.decoder.decode(e.payload,e.ts)};this.stopId=setInterval((()=>{if(e.length)if(this.dropping){for((t=e.shift()).type===b&&0===t.payload[1]&&r(t);!t.isIFrame&&e.length;)(t=e.shift()).type===b&&0===t.payload[1]&&r(t);t.isIFrame&&(this.dropping=!1,r(t))}else{var t=e[0];if(-1===this.getDelay(t.ts))e.shift(),r(t);else if(this.delay>n.opt.videoBuffer+n.opt.videoBufferDelay)this.resetDelay(),this.dropping=!0;else for(;e.length&&(t=e[0],this.getDelay(t.ts)>n.opt.videoBuffer);)e.shift(),r(t)}}),10)},close:function(){n.opt.debug&&console.log("Jessibuca: [worker]: close"),clearInterval(this.stopId),this.stopId=null,o.clear&&o.clear(),i.clear&&i.clear(),t.reset&&t.reset(),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1,this.webglObj&&(this.webglObj.destroy(),this.offscreenCanvas=null,this.offscreenCanvasGL=null,this.offscreenCanvasCtx=null),e=[],r=[],delete this.playAudioPlanar,delete this.draw},pushBuffer:function(r,t){t.type===b?e.push({ts:t.ts,payload:r,decoder:o,type:b}):t.type===_&&e.push({ts:t.ts,payload:r,decoder:i,type:_,isIFrame:t.isIFrame})}},o=new l.AudioDecoder(n),i=new l.VideoDecoder(n);postMessage({cmd:T}),self.onmessage=function(e){var r=e.data;switch(r.cmd){case T:try{n.opt=Object.assign(n.opt,JSON.parse(r.opt))}catch(e){}o.sample_rate=r.sampleRate,n.init();break;case k:n.pushBuffer(r.buffer,r.options);break;case S:o.decode(r.buffer,r.ts);break;case C:i.decode(r.buffer,r.ts);break;case P:n.close();break;case A:n.opt[r.key]=r.value}}}})); +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(require("path"),require("fs"),require("crypto")):"function"==typeof define&&define.amd?define(["path","fs","crypto"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).path,e.fs,e.crypto$1)}(this,function(e,r,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),i=n(r),a=n(t);function s(e,r){return e(r={exports:{}},r.exports),r.exports}var l=s(function(e){var r=void 0!==r?r:{},t=(r={print:function(e){console.log("Jessibuca: [worker]:",e)},printErr:function(e){console.warn("Jessibuca: [worker]:",e),postMessage({cmd:"wasmError",message:e})}},Object.assign({},r)),n="./this.program",s="object"==typeof window,l="function"==typeof importScripts,u="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,c=!s&&!u&&!l;if(r.ENVIRONMENT)throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");var d,f,p,m,h,g,v="";if(u){if("object"!=typeof process)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");v=l?o.default.dirname(v)+"/":__dirname+"/",g=()=>{h||(m=i.default,h=o.default)},d=function(e,r){return g(),e=h.normalize(e),m.readFileSync(e,r?void 0:"utf8")},p=e=>{var r=d(e,!0);return r.buffer||(r=new Uint8Array(r)),F(r.buffer),r},f=(e,r,t)=>{g(),e=h.normalize(e),m.readFile(e,function(e,n){e?t(e):r(n.buffer)})},process.argv.length>1&&(n=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),e.exports=r,process.on("uncaughtException",function(e){if(!(e instanceof At))throw e}),process.on("unhandledRejection",function(e){throw e}),r.inspect=function(){return"[Emscripten Module object]"}}else if(c){if("object"==typeof process||"object"==typeof window||"function"==typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");"undefined"!=typeof read&&(d=function(e){return read(e)}),p=function(e){let r;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(r=read(e,"binary"),F("object"==typeof r),r)},f=function(e,r,t){setTimeout(()=>r(p(e)),0)},"undefined"!=typeof scriptArgs&&scriptArgs,"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print)}else{if(!s&&!l)throw new Error("environment detection error");if(l?v=self.location.href:"undefined"!=typeof document&&document.currentScript&&(v=document.currentScript.src),v=0!==v.indexOf("blob:")?v.substr(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1):"","object"!=typeof window&&"function"!=typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");d=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},l&&(p=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),f=(e,r,t)=>{var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=()=>{200==n.status||0==n.status&&n.response?r(n.response):t()},n.onerror=t,n.send(null)}}var y,E,w,b=r.print||console.log.bind(console),_=r.printErr||console.warn.bind(console);function T(e){T.shown||(T.shown={}),T.shown[e]||(T.shown[e]=1,_(e))}function k(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ye("Module."+e+" has been replaced with plain "+t+" (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}})}function S(e,r){var t="'"+e+"' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)";return r&&(t+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"),t}function C(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ye(S(e,t))}})}function P(e,t){Object.getOwnPropertyDescriptor(r,e)||(r[e]=()=>ye(S(e,t)))}Object.assign(r,t),t=null,y="fetchSettings",Object.getOwnPropertyDescriptor(r,y)&&ye("`Module."+y+"` was supplied but `"+y+"` not included in INCOMING_MODULE_JS_API"),r.arguments&&r.arguments,k("arguments","arguments_"),r.thisProgram&&(n=r.thisProgram),k("thisProgram","thisProgram"),r.quit&&r.quit,k("quit","quit_"),F(void 0===r.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead"),F(void 0===r.read,"Module.read option was removed (modify read_ in JS)"),F(void 0===r.readAsync,"Module.readAsync option was removed (modify readAsync in JS)"),F(void 0===r.readBinary,"Module.readBinary option was removed (modify readBinary in JS)"),F(void 0===r.setWindowTitle,"Module.setWindowTitle option was removed (modify setWindowTitle in JS)"),F(void 0===r.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY"),k("read","read_"),k("readAsync","readAsync"),k("readBinary","readBinary"),k("setWindowTitle","setWindowTitle"),F(!c,"shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."),r.wasmBinary&&(E=r.wasmBinary),k("wasmBinary","wasmBinary"),r.noExitRuntime,k("noExitRuntime","noExitRuntime"),"object"!=typeof WebAssembly&&ye("no native wasm support detected");var A=!1;function F(e,r){e||ye("Assertion failed"+(r?": "+r:""))}var D="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function O(e,r,t){for(var n=r+t,o=r;e[o]&&!(o>=n);)++o;if(o-r>16&&e.buffer&&D)return D.decode(e.subarray(r,o));for(var i="";r>10,56320|1023&u)}}else i+=String.fromCharCode((31&a)<<6|s)}else i+=String.fromCharCode(a)}return i}function R(e,r){return e?O(U,e,r):""}function M(e,r,t,n){if(!(n>0))return 0;for(var o=t,i=t+n-1,a=0;a=55296&&s<=57343)s=65536+((1023&s)<<10)|1023&e.charCodeAt(++a);if(s<=127){if(t>=i)break;r[t++]=s}else if(s<=2047){if(t+1>=i)break;r[t++]=192|s>>6,r[t++]=128|63&s}else if(s<=65535){if(t+2>=i)break;r[t++]=224|s>>12,r[t++]=128|s>>6&63,r[t++]=128|63&s}else{if(t+3>=i)break;s>1114111&&T("Invalid Unicode code point 0x"+s.toString(16)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF)."),r[t++]=240|s>>18,r[t++]=128|s>>12&63,r[t++]=128|s>>6&63,r[t++]=128|63&s}}return r[t]=0,t-o}function N(e,r,t){return F("number"==typeof t,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),M(e,U,r,t)}function I(e){for(var r=0,t=0;t=55296&&n<=57343&&(n=65536+((1023&n)<<10)|1023&e.charCodeAt(++t)),n<=127?++r:r+=n<=2047?2:n<=65535?3:4}return r}var L,x,U,B,j,$,W,z,H,G="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0;function V(e,r){F(e%2==0,"Pointer passed to UTF16ToString must be aligned to two bytes!");for(var t=e,n=t>>1,o=n+r/2;!(n>=o)&&j[n];)++n;if((t=n<<1)-e>32&&G)return G.decode(U.subarray(e,t));for(var i="",a=0;!(a>=r/2);++a){var s=B[e+2*a>>1];if(0==s)break;i+=String.fromCharCode(s)}return i}function q(e,r,t){if(F(r%2==0,"Pointer passed to stringToUTF16 must be aligned to two bytes!"),F("number"==typeof t,"stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<2)return 0;for(var n=r,o=(t-=2)<2*e.length?t/2:e.length,i=0;i>1]=a,r+=2}return B[r>>1]=0,r-n}function Y(e){return 2*e.length}function X(e,r){F(e%4==0,"Pointer passed to UTF32ToString must be aligned to four bytes!");for(var t=0,n="";!(t>=r/4);){var o=$[e+4*t>>2];if(0==o)break;if(++t,o>=65536){var i=o-65536;n+=String.fromCharCode(55296|i>>10,56320|1023&i)}else n+=String.fromCharCode(o)}return n}function K(e,r,t){if(F(r%4==0,"Pointer passed to stringToUTF32 must be aligned to four bytes!"),F("number"==typeof t,"stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<4)return 0;for(var n=r,o=n+t-4,i=0;i=55296&&a<=57343)a=65536+((1023&a)<<10)|1023&e.charCodeAt(++i);if($[r>>2]=a,(r+=4)+4>o)break}return $[r>>2]=0,r-n}function J(e){for(var r=0,t=0;t=55296&&n<=57343&&++t,r+=4}return r}function Q(e){var r=I(e)+1,t=Et(r);return t&&M(e,x,t,r),t}function Z(e){L=e,r.HEAP8=x=new Int8Array(e),r.HEAP16=B=new Int16Array(e),r.HEAP32=$=new Int32Array(e),r.HEAPU8=U=new Uint8Array(e),r.HEAPU16=j=new Uint16Array(e),r.HEAPU32=W=new Uint32Array(e),r.HEAPF32=z=new Float32Array(e),r.HEAPF64=H=new Float64Array(e)}var ee=5242880;r.TOTAL_STACK&&F(ee===r.TOTAL_STACK,"the stack size can no longer be determined at runtime");var re,te=r.INITIAL_MEMORY||67108864;function ne(){var e=Pt();F(!(3&e)),$[e>>2]=34821223,$[e+4>>2]=2310721022,$[0]=1668509029}function oe(){if(!A){var e=Pt(),r=W[e>>2],t=W[e+4>>2];34821223==r&&2310721022==t||ye("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x"+t.toString(16)+" 0x"+r.toString(16)),1668509029!==$[0]&&ye("Runtime error: The application has corrupted its heap memory area (address zero)!")}}k("INITIAL_MEMORY","INITIAL_MEMORY"),F(te>=ee,"INITIAL_MEMORY should be larger than TOTAL_STACK, was "+te+"! (TOTAL_STACK="+ee+")"),F("undefined"!=typeof Int32Array&&"undefined"!=typeof Float64Array&&null!=Int32Array.prototype.subarray&&null!=Int32Array.prototype.set,"JS engine does not provide full typed array support"),F(!r.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally"),F(67108864==te,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically"),function(){var e=new Int16Array(1),r=new Int8Array(e.buffer);if(e[0]=25459,115!==r[0]||99!==r[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)"}();var ie=[],ae=[],se=[],le=!1;function ue(e){ie.unshift(e)}function ce(e){se.unshift(e)}F(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),F(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var de=0,fe=null,pe=null,me={};function he(e){for(var r=e;;){if(!me[e])return e;e=r+Math.random()}}function ge(e){de++,r.monitorRunDependencies&&r.monitorRunDependencies(de),e?(F(!me[e]),me[e]=1,null===fe&&"undefined"!=typeof setInterval&&(fe=setInterval(function(){if(A)return clearInterval(fe),void(fe=null);var e=!1;for(var r in me)e||(e=!0,_("still waiting on run dependencies:")),_("dependency: "+r);e&&_("(end of list)")},1e4))):_("warning: run dependency added without ID")}function ve(e){if(de--,r.monitorRunDependencies&&r.monitorRunDependencies(de),e?(F(me[e]),delete me[e]):_("warning: run dependency removed without ID"),0==de&&(null!==fe&&(clearInterval(fe),fe=null),pe)){var t=pe;pe=null,t()}}function ye(e){throw r.onAbort&&r.onAbort(e),_(e="Aborted("+e+")"),A=!0,new WebAssembly.RuntimeError(e)}var Ee,we,be;function _e(e){return e.startsWith("data:application/octet-stream;base64,")}function Te(e){return e.startsWith("file://")}function ke(e,t){return function(){var n=e,o=t;return t||(o=r.asm),F(le,"native function `"+n+"` called before runtime initialization"),o[e]||F(o[e],"exported native function `"+n+"` not found"),o[e].apply(null,arguments)}}function Se(e){try{if(e==Ee&&E)return new Uint8Array(E);if(p)return p(e);throw"both async and sync fetching of the wasm failed"}catch(e){ye(e)}}function Ce(e){for(;e.length>0;){var t=e.shift();if("function"!=typeof t){var n=t.func;"number"==typeof n?void 0===t.arg?Ae(n)():Ae(n)(t.arg):n(void 0===t.arg?null:t.arg)}else t(r)}}function Pe(e){return e.replace(/\b_Z[\w\d_]+/g,function(e){var r,t=(r=e,T("warning: build with -sDEMANGLE_SUPPORT to link in libcxxabi demangling"),r);return e===t?e:t+" ["+e+"]"})}function Ae(e){return re.get(e)}_e(Ee="decoder.wasm")||(Ee=function(e){return r.locateFile?r.locateFile(e,v):v+e}(Ee));var Fe={isAbs:e=>"/"===e.charAt(0),splitPath:e=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(e).slice(1),normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),t++):t&&(e.splice(n,1),t--)}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=Fe.isAbs(e),t="/"===e.substr(-1);return(e=Fe.normalizeArray(e.split("/").filter(e=>!!e),!r).join("/"))||r||(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=Fe.splitPath(e),t=r[0],n=r[1];return t||n?(n&&(n=n.substr(0,n.length-1)),t+n):"."},basename:e=>{if("/"===e)return"/";var r=(e=(e=Fe.normalize(e)).replace(/\/$/,"")).lastIndexOf("/");return-1===r?e:e.substr(r+1)},join:function(){var e=Array.prototype.slice.call(arguments,0);return Fe.normalize(e.join("/"))},join2:(e,r)=>Fe.normalize(e+"/"+r)};var De={resolve:function(){for(var e="",r=!1,t=arguments.length-1;t>=-1&&!r;t--){var n=t>=0?arguments[t]:Le.cwd();if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");if(!n)return"";e=n+"/"+e,r=Fe.isAbs(n)}return(r?"/":"")+(e=Fe.normalizeArray(e.split("/").filter(e=>!!e),!r).join("/"))||"."},relative:(e,r)=>{function t(e){for(var r=0;r=0&&""===e[t];t--);return r>t?[]:e.slice(r,t-r+1)}e=De.resolve(e).substr(1),r=De.resolve(r).substr(1);for(var n=t(e.split("/")),o=t(r.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s0?t.slice(0,n).toString("utf-8"):null}else"undefined"!=typeof window&&"function"==typeof window.prompt?null!==(r=window.prompt("Input: "))&&(r+="\n"):"function"==typeof readline&&null!==(r=readline())&&(r+="\n");if(!r)return null;e.input=gt(r,!0)}return e.input.shift()},put_char:function(e,r){null===r||10===r?(b(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(b(O(e.output,0)),e.output=[])}},default_tty1_ops:{put_char:function(e,r){null===r||10===r?(_(O(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(_(O(e.output,0)),e.output=[])}}};function Re(e){e=function(e,r){return F(r,"alignment argument is required"),Math.ceil(e/r)*r}(e,65536);var r=kt(65536,e);return r?(function(e,r){U.fill(0,e,e+r)}(r,e),r):0}var Me={ops_table:null,mount:function(e){return Me.createNode(null,"/",16895,0)},createNode:function(e,r,t,n){if(Le.isBlkdev(t)||Le.isFIFO(t))throw new Le.ErrnoError(63);Me.ops_table||(Me.ops_table={dir:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr,lookup:Me.node_ops.lookup,mknod:Me.node_ops.mknod,rename:Me.node_ops.rename,unlink:Me.node_ops.unlink,rmdir:Me.node_ops.rmdir,readdir:Me.node_ops.readdir,symlink:Me.node_ops.symlink},stream:{llseek:Me.stream_ops.llseek}},file:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr},stream:{llseek:Me.stream_ops.llseek,read:Me.stream_ops.read,write:Me.stream_ops.write,allocate:Me.stream_ops.allocate,mmap:Me.stream_ops.mmap,msync:Me.stream_ops.msync}},link:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr,readlink:Me.node_ops.readlink},stream:{}},chrdev:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr},stream:Le.chrdev_stream_ops}});var o=Le.createNode(e,r,t,n);return Le.isDir(o.mode)?(o.node_ops=Me.ops_table.dir.node,o.stream_ops=Me.ops_table.dir.stream,o.contents={}):Le.isFile(o.mode)?(o.node_ops=Me.ops_table.file.node,o.stream_ops=Me.ops_table.file.stream,o.usedBytes=0,o.contents=null):Le.isLink(o.mode)?(o.node_ops=Me.ops_table.link.node,o.stream_ops=Me.ops_table.link.stream):Le.isChrdev(o.mode)&&(o.node_ops=Me.ops_table.chrdev.node,o.stream_ops=Me.ops_table.chrdev.stream),o.timestamp=Date.now(),e&&(e.contents[r]=o,e.timestamp=o.timestamp),o},getFileDataAsTypedArray:function(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage:function(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){r=Math.max(r,t*(t<1048576?2:1.125)>>>0),0!=t&&(r=Math.max(r,256));var n=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(n.subarray(0,e.usedBytes),0)}},resizeFileStorage:function(e,r){if(e.usedBytes!=r)if(0==r)e.contents=null,e.usedBytes=0;else{var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r}},node_ops:{getattr:function(e){var r={};return r.dev=Le.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,Le.isDir(e.mode)?r.size=4096:Le.isFile(e.mode)?r.size=e.usedBytes:Le.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr:function(e,r){void 0!==r.mode&&(e.mode=r.mode),void 0!==r.timestamp&&(e.timestamp=r.timestamp),void 0!==r.size&&Me.resizeFileStorage(e,r.size)},lookup:function(e,r){throw Le.genericErrors[44]},mknod:function(e,r,t,n){return Me.createNode(e,r,t,n)},rename:function(e,r,t){if(Le.isDir(e.mode)){var n;try{n=Le.lookupNode(r,t)}catch(e){}if(n)for(var o in n.contents)throw new Le.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp,e.parent=r},unlink:function(e,r){delete e.contents[r],e.timestamp=Date.now()},rmdir:function(e,r){var t=Le.lookupNode(e,r);for(var n in t.contents)throw new Le.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now()},readdir:function(e){var r=[".",".."];for(var t in e.contents)e.contents.hasOwnProperty(t)&&r.push(t);return r},symlink:function(e,r,t){var n=Me.createNode(e,r,41471,0);return n.link=t,n},readlink:function(e){if(!Le.isLink(e.mode))throw new Le.ErrnoError(28);return e.link}},stream_ops:{read:function(e,r,t,n,o){var i=e.node.contents;if(o>=e.node.usedBytes)return 0;var a=Math.min(e.node.usedBytes-o,n);if(F(a>=0),a>8&&i.subarray)r.set(i.subarray(o,o+a),t);else for(var s=0;s0||n+t{if(!(e=De.resolve(Le.cwd(),e)))return{path:"",node:null};if((r=Object.assign({follow_mount:!0,recurse_count:0},r)).recurse_count>8)throw new Le.ErrnoError(32);for(var t=Fe.normalizeArray(e.split("/").filter(e=>!!e),!1),n=Le.root,o="/",i=0;i40)throw new Le.ErrnoError(32)}}return{path:o,node:n}},getPath:e=>{for(var r;;){if(Le.isRoot(e)){var t=e.mount.mountpoint;return r?"/"!==t[t.length-1]?t+"/"+r:t+r:t}r=r?e.name+"/"+r:e.name,e=e.parent}},hashName:(e,r)=>{for(var t=0,n=0;n>>0)%Le.nameTable.length},hashAddNode:e=>{var r=Le.hashName(e.parent.id,e.name);e.name_next=Le.nameTable[r],Le.nameTable[r]=e},hashRemoveNode:e=>{var r=Le.hashName(e.parent.id,e.name);if(Le.nameTable[r]===e)Le.nameTable[r]=e.name_next;else for(var t=Le.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next}},lookupNode:(e,r)=>{var t=Le.mayLookup(e);if(t)throw new Le.ErrnoError(t,e);for(var n=Le.hashName(e.id,r),o=Le.nameTable[n];o;o=o.name_next){var i=o.name;if(o.parent.id===e.id&&i===r)return o}return Le.lookup(e,r)},createNode:(e,r,t,n)=>{F("object"==typeof e);var o=new Le.FSNode(e,r,t,n);return Le.hashAddNode(o),o},destroyNode:e=>{Le.hashRemoveNode(e)},isRoot:e=>e===e.parent,isMountpoint:e=>!!e.mounted,isFile:e=>32768==(61440&e),isDir:e=>16384==(61440&e),isLink:e=>40960==(61440&e),isChrdev:e=>8192==(61440&e),isBlkdev:e=>24576==(61440&e),isFIFO:e=>4096==(61440&e),isSocket:e=>!(49152&~e),flagModes:{r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},modeStringToFlags:e=>{var r=Le.flagModes[e];if(void 0===r)throw new Error("Unknown file open mode: "+e);return r},flagsToPermissionString:e=>{var r=["r","w","rw"][3&e];return 512&e&&(r+="w"),r},nodePermissions:(e,r)=>Le.ignorePermissions||(!r.includes("r")||292&e.mode)&&(!r.includes("w")||146&e.mode)&&(!r.includes("x")||73&e.mode)?0:2,mayLookup:e=>{var r=Le.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate:(e,r)=>{try{Le.lookupNode(e,r);return 20}catch(e){}return Le.nodePermissions(e,"wx")},mayDelete:(e,r,t)=>{var n;try{n=Le.lookupNode(e,r)}catch(e){return e.errno}var o=Le.nodePermissions(e,"wx");if(o)return o;if(t){if(!Le.isDir(n.mode))return 54;if(Le.isRoot(n)||Le.getPath(n)===Le.cwd())return 10}else if(Le.isDir(n.mode))return 31;return 0},mayOpen:(e,r)=>e?Le.isLink(e.mode)?32:Le.isDir(e.mode)&&("r"!==Le.flagsToPermissionString(r)||512&r)?31:Le.nodePermissions(e,Le.flagsToPermissionString(r)):44,MAX_OPEN_FDS:4096,nextfd:(e=0,r=Le.MAX_OPEN_FDS)=>{for(var t=e;t<=r;t++)if(!Le.streams[t])return t;throw new Le.ErrnoError(33)},getStream:e=>Le.streams[e],createStream:(e,r,t)=>{Le.FSStream||(Le.FSStream=function(){this.shared={}},Le.FSStream.prototype={object:{get:function(){return this.node},set:function(e){this.node=e}},isRead:{get:function(){return 1!=(2097155&this.flags)}},isWrite:{get:function(){return!!(2097155&this.flags)}},isAppend:{get:function(){return 1024&this.flags}},flags:{get:function(){return this.shared.flags},set:function(e){this.shared.flags=e}},position:{get function(){return this.shared.position},set:function(e){this.shared.position=e}}}),e=Object.assign(new Le.FSStream,e);var n=Le.nextfd(r,t);return e.fd=n,Le.streams[n]=e,e},closeStream:e=>{Le.streams[e]=null},chrdev_stream_ops:{open:e=>{var r=Le.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open&&e.stream_ops.open(e)},llseek:()=>{throw new Le.ErrnoError(70)}},major:e=>e>>8,minor:e=>255&e,makedev:(e,r)=>e<<8|r,registerDevice:(e,r)=>{Le.devices[e]={stream_ops:r}},getDevice:e=>Le.devices[e],getMounts:e=>{for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push.apply(t,n.mounts)}return r},syncfs:(e,r)=>{"function"==typeof e&&(r=e,e=!1),Le.syncFSRequests++,Le.syncFSRequests>1&&_("warning: "+Le.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work");var t=Le.getMounts(Le.root.mount),n=0;function o(e){return F(Le.syncFSRequests>0),Le.syncFSRequests--,r(e)}function i(e){if(e)return i.errored?void 0:(i.errored=!0,o(e));++n>=t.length&&o(null)}t.forEach(r=>{if(!r.type.syncfs)return i(null);r.type.syncfs(r,e,i)})},mount:(e,r,t)=>{if("string"==typeof e)throw e;var n,o="/"===t,i=!t;if(o&&Le.root)throw new Le.ErrnoError(10);if(!o&&!i){var a=Le.lookupPath(t,{follow_mount:!1});if(t=a.path,n=a.node,Le.isMountpoint(n))throw new Le.ErrnoError(10);if(!Le.isDir(n.mode))throw new Le.ErrnoError(54)}var s={type:e,opts:r,mountpoint:t,mounts:[]},l=e.mount(s);return l.mount=s,s.root=l,o?Le.root=l:n&&(n.mounted=s,n.mount&&n.mount.mounts.push(s)),l},unmount:e=>{var r=Le.lookupPath(e,{follow_mount:!1});if(!Le.isMountpoint(r.node))throw new Le.ErrnoError(28);var t=r.node,n=t.mounted,o=Le.getMounts(n);Object.keys(Le.nameTable).forEach(e=>{for(var r=Le.nameTable[e];r;){var t=r.name_next;o.includes(r.mount)&&Le.destroyNode(r),r=t}}),t.mounted=null;var i=t.mount.mounts.indexOf(n);F(-1!==i),t.mount.mounts.splice(i,1)},lookup:(e,r)=>e.node_ops.lookup(e,r),mknod:(e,r,t)=>{var n=Le.lookupPath(e,{parent:!0}).node,o=Fe.basename(e);if(!o||"."===o||".."===o)throw new Le.ErrnoError(28);var i=Le.mayCreate(n,o);if(i)throw new Le.ErrnoError(i);if(!n.node_ops.mknod)throw new Le.ErrnoError(63);return n.node_ops.mknod(n,o,r,t)},create:(e,r)=>(r=void 0!==r?r:438,r&=4095,r|=32768,Le.mknod(e,r,0)),mkdir:(e,r)=>(r=void 0!==r?r:511,r&=1023,r|=16384,Le.mknod(e,r,0)),mkdirTree:(e,r)=>{for(var t=e.split("/"),n="",o=0;o(void 0===t&&(t=r,r=438),r|=8192,Le.mknod(e,r,t)),symlink:(e,r)=>{if(!De.resolve(e))throw new Le.ErrnoError(44);var t=Le.lookupPath(r,{parent:!0}).node;if(!t)throw new Le.ErrnoError(44);var n=Fe.basename(r),o=Le.mayCreate(t,n);if(o)throw new Le.ErrnoError(o);if(!t.node_ops.symlink)throw new Le.ErrnoError(63);return t.node_ops.symlink(t,n,e)},rename:(e,r)=>{var t,n,o=Fe.dirname(e),i=Fe.dirname(r),a=Fe.basename(e),s=Fe.basename(r);if(t=Le.lookupPath(e,{parent:!0}).node,n=Le.lookupPath(r,{parent:!0}).node,!t||!n)throw new Le.ErrnoError(44);if(t.mount!==n.mount)throw new Le.ErrnoError(75);var l,u=Le.lookupNode(t,a),c=De.relative(e,i);if("."!==c.charAt(0))throw new Le.ErrnoError(28);if("."!==(c=De.relative(r,o)).charAt(0))throw new Le.ErrnoError(55);try{l=Le.lookupNode(n,s)}catch(e){}if(u!==l){var d=Le.isDir(u.mode),f=Le.mayDelete(t,a,d);if(f)throw new Le.ErrnoError(f);if(f=l?Le.mayDelete(n,s,d):Le.mayCreate(n,s))throw new Le.ErrnoError(f);if(!t.node_ops.rename)throw new Le.ErrnoError(63);if(Le.isMountpoint(u)||l&&Le.isMountpoint(l))throw new Le.ErrnoError(10);if(n!==t&&(f=Le.nodePermissions(t,"w")))throw new Le.ErrnoError(f);Le.hashRemoveNode(u);try{t.node_ops.rename(u,n,s)}catch(e){throw e}finally{Le.hashAddNode(u)}}},rmdir:e=>{var r=Le.lookupPath(e,{parent:!0}).node,t=Fe.basename(e),n=Le.lookupNode(r,t),o=Le.mayDelete(r,t,!0);if(o)throw new Le.ErrnoError(o);if(!r.node_ops.rmdir)throw new Le.ErrnoError(63);if(Le.isMountpoint(n))throw new Le.ErrnoError(10);r.node_ops.rmdir(r,t),Le.destroyNode(n)},readdir:e=>{var r=Le.lookupPath(e,{follow:!0}).node;if(!r.node_ops.readdir)throw new Le.ErrnoError(54);return r.node_ops.readdir(r)},unlink:e=>{var r=Le.lookupPath(e,{parent:!0}).node;if(!r)throw new Le.ErrnoError(44);var t=Fe.basename(e),n=Le.lookupNode(r,t),o=Le.mayDelete(r,t,!1);if(o)throw new Le.ErrnoError(o);if(!r.node_ops.unlink)throw new Le.ErrnoError(63);if(Le.isMountpoint(n))throw new Le.ErrnoError(10);r.node_ops.unlink(r,t),Le.destroyNode(n)},readlink:e=>{var r=Le.lookupPath(e).node;if(!r)throw new Le.ErrnoError(44);if(!r.node_ops.readlink)throw new Le.ErrnoError(28);return De.resolve(Le.getPath(r.parent),r.node_ops.readlink(r))},stat:(e,r)=>{var t=Le.lookupPath(e,{follow:!r}).node;if(!t)throw new Le.ErrnoError(44);if(!t.node_ops.getattr)throw new Le.ErrnoError(63);return t.node_ops.getattr(t)},lstat:e=>Le.stat(e,!0),chmod:(e,r,t)=>{var n;"string"==typeof e?n=Le.lookupPath(e,{follow:!t}).node:n=e;if(!n.node_ops.setattr)throw new Le.ErrnoError(63);n.node_ops.setattr(n,{mode:4095&r|-4096&n.mode,timestamp:Date.now()})},lchmod:(e,r)=>{Le.chmod(e,r,!0)},fchmod:(e,r)=>{var t=Le.getStream(e);if(!t)throw new Le.ErrnoError(8);Le.chmod(t.node,r)},chown:(e,r,t,n)=>{var o;"string"==typeof e?o=Le.lookupPath(e,{follow:!n}).node:o=e;if(!o.node_ops.setattr)throw new Le.ErrnoError(63);o.node_ops.setattr(o,{timestamp:Date.now()})},lchown:(e,r,t)=>{Le.chown(e,r,t,!0)},fchown:(e,r,t)=>{var n=Le.getStream(e);if(!n)throw new Le.ErrnoError(8);Le.chown(n.node,r,t)},truncate:(e,r)=>{if(r<0)throw new Le.ErrnoError(28);var t;"string"==typeof e?t=Le.lookupPath(e,{follow:!0}).node:t=e;if(!t.node_ops.setattr)throw new Le.ErrnoError(63);if(Le.isDir(t.mode))throw new Le.ErrnoError(31);if(!Le.isFile(t.mode))throw new Le.ErrnoError(28);var n=Le.nodePermissions(t,"w");if(n)throw new Le.ErrnoError(n);t.node_ops.setattr(t,{size:r,timestamp:Date.now()})},ftruncate:(e,r)=>{var t=Le.getStream(e);if(!t)throw new Le.ErrnoError(8);if(!(2097155&t.flags))throw new Le.ErrnoError(28);Le.truncate(t.node,r)},utime:(e,r,t)=>{var n=Le.lookupPath(e,{follow:!0}).node;n.node_ops.setattr(n,{timestamp:Math.max(r,t)})},open:(e,t,n,o,i)=>{if(""===e)throw new Le.ErrnoError(44);var a;if(n=void 0===n?438:n,n=64&(t="string"==typeof t?Le.modeStringToFlags(t):t)?4095&n|32768:0,"object"==typeof e)a=e;else{e=Fe.normalize(e);try{a=Le.lookupPath(e,{follow:!(131072&t)}).node}catch(e){}}var s=!1;if(64&t)if(a){if(128&t)throw new Le.ErrnoError(20)}else a=Le.mknod(e,n,0),s=!0;if(!a)throw new Le.ErrnoError(44);if(Le.isChrdev(a.mode)&&(t&=-513),65536&t&&!Le.isDir(a.mode))throw new Le.ErrnoError(54);if(!s){var l=Le.mayOpen(a,t);if(l)throw new Le.ErrnoError(l)}512&t&&Le.truncate(a,0),t&=-131713;var u=Le.createStream({node:a,path:Le.getPath(a),flags:t,seekable:!0,position:0,stream_ops:a.stream_ops,ungotten:[],error:!1},o,i);return u.stream_ops.open&&u.stream_ops.open(u),!r.logReadFiles||1&t||(Le.readFiles||(Le.readFiles={}),e in Le.readFiles||(Le.readFiles[e]=1)),u},close:e=>{if(Le.isClosed(e))throw new Le.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e)}catch(e){throw e}finally{Le.closeStream(e.fd)}e.fd=null},isClosed:e=>null===e.fd,llseek:(e,r,t)=>{if(Le.isClosed(e))throw new Le.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new Le.ErrnoError(70);if(0!=t&&1!=t&&2!=t)throw new Le.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read:(e,r,t,n,o)=>{if(n<0||o<0)throw new Le.ErrnoError(28);if(Le.isClosed(e))throw new Le.ErrnoError(8);if(1==(2097155&e.flags))throw new Le.ErrnoError(8);if(Le.isDir(e.node.mode))throw new Le.ErrnoError(31);if(!e.stream_ops.read)throw new Le.ErrnoError(28);var i=void 0!==o;if(i){if(!e.seekable)throw new Le.ErrnoError(70)}else o=e.position;var a=e.stream_ops.read(e,r,t,n,o);return i||(e.position+=a),a},write:(e,r,t,n,o,i)=>{if(n<0||o<0)throw new Le.ErrnoError(28);if(Le.isClosed(e))throw new Le.ErrnoError(8);if(!(2097155&e.flags))throw new Le.ErrnoError(8);if(Le.isDir(e.node.mode))throw new Le.ErrnoError(31);if(!e.stream_ops.write)throw new Le.ErrnoError(28);e.seekable&&1024&e.flags&&Le.llseek(e,0,2);var a=void 0!==o;if(a){if(!e.seekable)throw new Le.ErrnoError(70)}else o=e.position;var s=e.stream_ops.write(e,r,t,n,o,i);return a||(e.position+=s),s},allocate:(e,r,t)=>{if(Le.isClosed(e))throw new Le.ErrnoError(8);if(r<0||t<=0)throw new Le.ErrnoError(28);if(!(2097155&e.flags))throw new Le.ErrnoError(8);if(!Le.isFile(e.node.mode)&&!Le.isDir(e.node.mode))throw new Le.ErrnoError(43);if(!e.stream_ops.allocate)throw new Le.ErrnoError(138);e.stream_ops.allocate(e,r,t)},mmap:(e,r,t,n,o,i)=>{if(2&o&&!(2&i)&&2!=(2097155&e.flags))throw new Le.ErrnoError(2);if(1==(2097155&e.flags))throw new Le.ErrnoError(2);if(!e.stream_ops.mmap)throw new Le.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,o,i)},msync:(e,r,t,n,o)=>e&&e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,o):0,munmap:e=>0,ioctl:(e,r,t)=>{if(!e.stream_ops.ioctl)throw new Le.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile:(e,r={})=>{if(r.flags=r.flags||0,r.encoding=r.encoding||"binary","utf8"!==r.encoding&&"binary"!==r.encoding)throw new Error('Invalid encoding type "'+r.encoding+'"');var t,n=Le.open(e,r.flags),o=Le.stat(e).size,i=new Uint8Array(o);return Le.read(n,i,0,o,0),"utf8"===r.encoding?t=O(i,0):"binary"===r.encoding&&(t=i),Le.close(n),t},writeFile:(e,r,t={})=>{t.flags=t.flags||577;var n=Le.open(e,t.flags,t.mode);if("string"==typeof r){var o=new Uint8Array(I(r)+1),i=M(r,o,0,o.length);Le.write(n,o,0,i,void 0,t.canOwn)}else{if(!ArrayBuffer.isView(r))throw new Error("Unsupported data type");Le.write(n,r,0,r.byteLength,void 0,t.canOwn)}Le.close(n)},cwd:()=>Le.currentPath,chdir:e=>{var r=Le.lookupPath(e,{follow:!0});if(null===r.node)throw new Le.ErrnoError(44);if(!Le.isDir(r.node.mode))throw new Le.ErrnoError(54);var t=Le.nodePermissions(r.node,"x");if(t)throw new Le.ErrnoError(t);Le.currentPath=r.path},createDefaultDirectories:()=>{Le.mkdir("/tmp"),Le.mkdir("/home"),Le.mkdir("/home/web_user")},createDefaultDevices:()=>{Le.mkdir("/dev"),Le.registerDevice(Le.makedev(1,3),{read:()=>0,write:(e,r,t,n,o)=>n}),Le.mkdev("/dev/null",Le.makedev(1,3)),Oe.register(Le.makedev(5,0),Oe.default_tty_ops),Oe.register(Le.makedev(6,0),Oe.default_tty1_ops),Le.mkdev("/dev/tty",Le.makedev(5,0)),Le.mkdev("/dev/tty1",Le.makedev(6,0));var e=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var e=new Uint8Array(1);return function(){return crypto.getRandomValues(e),e[0]}}if(u)try{var r=a.default;return function(){return r.randomBytes(1)[0]}}catch(e){}return function(){ye("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };")}}();Le.createDevice("/dev","random",e),Le.createDevice("/dev","urandom",e),Le.mkdir("/dev/shm"),Le.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{Le.mkdir("/proc");var e=Le.mkdir("/proc/self");Le.mkdir("/proc/self/fd"),Le.mount({mount:()=>{var r=Le.createNode(e,"fd",16895,73);return r.node_ops={lookup:(e,r)=>{var t=+r,n=Le.getStream(t);if(!n)throw new Le.ErrnoError(8);var o={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>n.path}};return o.parent=o,o}},r}},{},"/proc/self/fd")},createStandardStreams:()=>{r.stdin?Le.createDevice("/dev","stdin",r.stdin):Le.symlink("/dev/tty","/dev/stdin"),r.stdout?Le.createDevice("/dev","stdout",null,r.stdout):Le.symlink("/dev/tty","/dev/stdout"),r.stderr?Le.createDevice("/dev","stderr",null,r.stderr):Le.symlink("/dev/tty1","/dev/stderr");var e=Le.open("/dev/stdin",0),t=Le.open("/dev/stdout",1),n=Le.open("/dev/stderr",1);F(0===e.fd,"invalid handle for stdin ("+e.fd+")"),F(1===t.fd,"invalid handle for stdout ("+t.fd+")"),F(2===n.fd,"invalid handle for stderr ("+n.fd+")")},ensureErrnoError:()=>{Le.ErrnoError||(Le.ErrnoError=function(e,r){this.node=r,this.setErrno=function(e){for(var r in this.errno=e,Ie)if(Ie[r]===e){this.code=r;break}},this.setErrno(e),this.message=Ne[e],this.stack&&(Object.defineProperty(this,"stack",{value:(new Error).stack,writable:!0}),this.stack=Pe(this.stack))},Le.ErrnoError.prototype=new Error,Le.ErrnoError.prototype.constructor=Le.ErrnoError,[44].forEach(e=>{Le.genericErrors[e]=new Le.ErrnoError(e),Le.genericErrors[e].stack=""}))},staticInit:()=>{Le.ensureErrnoError(),Le.nameTable=new Array(4096),Le.mount(Me,{},"/"),Le.createDefaultDirectories(),Le.createDefaultDevices(),Le.createSpecialDirectories(),Le.filesystems={MEMFS:Me}},init:(e,t,n)=>{F(!Le.init.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"),Le.init.initialized=!0,Le.ensureErrnoError(),r.stdin=e||r.stdin,r.stdout=t||r.stdout,r.stderr=n||r.stderr,Le.createStandardStreams()},quit:()=>{Le.init.initialized=!1,Tt();for(var e=0;e{var t=0;return e&&(t|=365),r&&(t|=146),t},findObject:(e,r)=>{var t=Le.analyzePath(e,r);return t.exists?t.object:null},analyzePath:(e,r)=>{try{e=(n=Le.lookupPath(e,{follow:!r})).path}catch(e){}var t={isRoot:!1,exists:!1,error:0,name:null,path:null,object:null,parentExists:!1,parentPath:null,parentObject:null};try{var n=Le.lookupPath(e,{parent:!0});t.parentExists=!0,t.parentPath=n.path,t.parentObject=n.node,t.name=Fe.basename(e),n=Le.lookupPath(e,{follow:!r}),t.exists=!0,t.path=n.path,t.object=n.node,t.name=n.node.name,t.isRoot="/"===n.path}catch(e){t.error=e.errno}return t},createPath:(e,r,t,n)=>{e="string"==typeof e?e:Le.getPath(e);for(var o=r.split("/").reverse();o.length;){var i=o.pop();if(i){var a=Fe.join2(e,i);try{Le.mkdir(a)}catch(e){}e=a}}return a},createFile:(e,r,t,n,o)=>{var i=Fe.join2("string"==typeof e?e:Le.getPath(e),r),a=Le.getMode(n,o);return Le.create(i,a)},createDataFile:(e,r,t,n,o,i)=>{var a=r;e&&(e="string"==typeof e?e:Le.getPath(e),a=r?Fe.join2(e,r):e);var s=Le.getMode(n,o),l=Le.create(a,s);if(t){if("string"==typeof t){for(var u=new Array(t.length),c=0,d=t.length;c{var o=Fe.join2("string"==typeof e?e:Le.getPath(e),r),i=Le.getMode(!!t,!!n);Le.createDevice.major||(Le.createDevice.major=64);var a=Le.makedev(Le.createDevice.major++,0);return Le.registerDevice(a,{open:e=>{e.seekable=!1},close:e=>{n&&n.buffer&&n.buffer.length&&n(10)},read:(e,r,n,o,i)=>{for(var a=0,s=0;s{for(var a=0;a{if(e.isDevice||e.isFolder||e.link||e.contents)return!0;if("undefined"!=typeof XMLHttpRequest)throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");if(!d)throw new Error("Cannot load without read() or XMLHttpRequest.");try{e.contents=gt(d(e.url),!0),e.usedBytes=e.contents.length}catch(e){throw new Le.ErrnoError(29)}},createLazyFile:(e,r,t,n,o)=>{function i(){this.lengthKnown=!1,this.chunks=[]}if(i.prototype.get=function(e){if(!(e>this.length-1||e<0)){var r=e%this.chunkSize,t=e/this.chunkSize|0;return this.getter(t)[r]}},i.prototype.setDataGetter=function(e){this.getter=e},i.prototype.cacheLength=function(){var e=new XMLHttpRequest;if(e.open("HEAD",t,!1),e.send(null),!(e.status>=200&&e.status<300||304===e.status))throw new Error("Couldn't load "+t+". Status: "+e.status);var r,n=Number(e.getResponseHeader("Content-length")),o=(r=e.getResponseHeader("Accept-Ranges"))&&"bytes"===r,i=(r=e.getResponseHeader("Content-Encoding"))&&"gzip"===r,a=1048576;o||(a=n);var s=this;s.setDataGetter(e=>{var r=e*a,o=(e+1)*a-1;if(o=Math.min(o,n-1),void 0===s.chunks[e]&&(s.chunks[e]=((e,r)=>{if(e>r)throw new Error("invalid range ("+e+", "+r+") or no bytes requested!");if(r>n-1)throw new Error("only "+n+" bytes available! programmer error!");var o=new XMLHttpRequest;if(o.open("GET",t,!1),n!==a&&o.setRequestHeader("Range","bytes="+e+"-"+r),o.responseType="arraybuffer",o.overrideMimeType&&o.overrideMimeType("text/plain; charset=x-user-defined"),o.send(null),!(o.status>=200&&o.status<300||304===o.status))throw new Error("Couldn't load "+t+". Status: "+o.status);return void 0!==o.response?new Uint8Array(o.response||[]):gt(o.responseText||"",!0)})(r,o)),void 0===s.chunks[e])throw new Error("doXHR failed!");return s.chunks[e]}),!i&&n||(a=n=1,n=this.getter(0).length,a=n,b("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=n,this._chunkSize=a,this.lengthKnown=!0},"undefined"!=typeof XMLHttpRequest){if(!l)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var a=new i;Object.defineProperties(a,{length:{get:function(){return this.lengthKnown||this.cacheLength(),this._length}},chunkSize:{get:function(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}});var s={isDevice:!1,contents:a}}else s={isDevice:!1,url:t};var u=Le.createFile(e,r,s,n,o);s.contents?u.contents=s.contents:s.url&&(u.contents=null,u.url=s.url),Object.defineProperties(u,{usedBytes:{get:function(){return this.contents.length}}});var c={};return Object.keys(u.stream_ops).forEach(e=>{var r=u.stream_ops[e];c[e]=function(){return Le.forceLoadFile(u),r.apply(null,arguments)}}),c.read=(e,r,t,n,o)=>{Le.forceLoadFile(u);var i=e.node.contents;if(o>=i.length)return 0;var a=Math.min(i.length-o,n);if(F(a>=0),i.slice)for(var s=0;s{var c=r?De.resolve(Fe.join2(e,r)):e,d=he("cp "+c);function p(t){function f(t){u&&u(),s||Le.createDataFile(e,r,t,n,o,l),i&&i(),ve(d)}Browser.handledByPreloadPlugin(t,c,f,()=>{a&&a(),ve(d)})||f(t)}ge(d),"string"==typeof t?function(e,r,t,n){var o=n?"":he("al "+e);f(e,function(t){F(t,'Loading data file "'+e+'" failed (no arrayBuffer).'),r(new Uint8Array(t)),o&&ve(o)},function(r){if(!t)throw'Loading data file "'+e+'" failed.';t()}),o&&ge(o)}(t,e=>p(e),a):p(t)},indexedDB:()=>window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,DB_NAME:()=>"EM_FS_"+window.location.pathname,DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Le.indexedDB();try{var o=n.open(Le.DB_NAME(),Le.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=()=>{b("creating db"),o.result.createObjectStore(Le.DB_STORE_NAME)},o.onsuccess=()=>{var n=o.result.transaction([Le.DB_STORE_NAME],"readwrite"),i=n.objectStore(Le.DB_STORE_NAME),a=0,s=0,l=e.length;function u(){0==s?r():t()}e.forEach(e=>{var r=i.put(Le.analyzePath(e).object.contents,e);r.onsuccess=()=>{++a+s==l&&u()},r.onerror=()=>{s++,a+s==l&&u()}}),n.onerror=t},o.onerror=t},loadFilesFromDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Le.indexedDB();try{var o=n.open(Le.DB_NAME(),Le.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=t,o.onsuccess=()=>{var n=o.result;try{var i=n.transaction([Le.DB_STORE_NAME],"readonly")}catch(e){return void t(e)}var a=i.objectStore(Le.DB_STORE_NAME),s=0,l=0,u=e.length;function c(){0==l?r():t()}e.forEach(e=>{var r=a.get(e);r.onsuccess=()=>{Le.analyzePath(e).exists&&Le.unlink(e),Le.createDataFile(Fe.dirname(e),Fe.basename(e),r.result,!0,!0,!0),++s+l==u&&c()},r.onerror=()=>{l++,s+l==u&&c()}}),i.onerror=t},o.onerror=t},absolutePath:()=>{ye("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder:()=>{ye("FS.createFolder has been removed; use FS.mkdir instead")},createLink:()=>{ye("FS.createLink has been removed; use FS.symlink instead")},joinPath:()=>{ye("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc:()=>{ye("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath:()=>{ye("FS.standardizePath has been removed; use PATH.normalize instead")}},xe={DEFAULT_POLLMASK:5,calculateAt:function(e,r,t){if(Fe.isAbs(r))return r;var n;if(-100===e)n=Le.cwd();else{var o=Le.getStream(e);if(!o)throw new Le.ErrnoError(8);n=o.path}if(0==r.length){if(!t)throw new Le.ErrnoError(44);return n}return Fe.join2(n,r)},doStat:function(e,r,t){try{var n=e(r)}catch(e){if(e&&e.node&&Fe.normalize(r)!==Fe.normalize(Le.getPath(e.node)))return-54;throw e}return $[t>>2]=n.dev,$[t+4>>2]=0,$[t+8>>2]=n.ino,$[t+12>>2]=n.mode,$[t+16>>2]=n.nlink,$[t+20>>2]=n.uid,$[t+24>>2]=n.gid,$[t+28>>2]=n.rdev,$[t+32>>2]=0,be=[n.size>>>0,(we=n.size,+Math.abs(we)>=1?we>0?(0|Math.min(+Math.floor(we/4294967296),4294967295))>>>0:~~+Math.ceil((we-+(~~we>>>0))/4294967296)>>>0:0)],$[t+40>>2]=be[0],$[t+44>>2]=be[1],$[t+48>>2]=4096,$[t+52>>2]=n.blocks,$[t+56>>2]=n.atime.getTime()/1e3|0,$[t+60>>2]=0,$[t+64>>2]=n.mtime.getTime()/1e3|0,$[t+68>>2]=0,$[t+72>>2]=n.ctime.getTime()/1e3|0,$[t+76>>2]=0,be=[n.ino>>>0,(we=n.ino,+Math.abs(we)>=1?we>0?(0|Math.min(+Math.floor(we/4294967296),4294967295))>>>0:~~+Math.ceil((we-+(~~we>>>0))/4294967296)>>>0:0)],$[t+80>>2]=be[0],$[t+84>>2]=be[1],0},doMsync:function(e,r,t,n,o){var i=U.slice(e,e+t);Le.msync(r,i,o,t,n)},doMknod:function(e,r,t){switch(61440&r){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}return Le.mknod(e,r,t),0},doReadlink:function(e,r,t){if(t<=0)return-28;var n=Le.readlink(e),o=Math.min(t,I(n)),i=x[r+o];return N(n,r,t+1),x[r+o]=i,o},doAccess:function(e,r){if(-8&r)return-28;var t=Le.lookupPath(e,{follow:!0}).node;if(!t)return-44;var n="";return 4&r&&(n+="r"),2&r&&(n+="w"),1&r&&(n+="x"),n&&Le.nodePermissions(t,n)?-2:0},doReadv:function(e,r,t,n){for(var o=0,i=0;i>2],s=$[r+4>>2];r+=8;var l=Le.read(e,x,a,s,n);if(l<0)return-1;if(o+=l,l>2],s=$[r+4>>2];r+=8;var l=Le.write(e,x,a,s,n);if(l<0)return-1;o+=l}return o},varargs:void 0,get:function(){return F(null!=xe.varargs),xe.varargs+=4,$[xe.varargs-4>>2]},getStr:function(e){return R(e)},getStreamFromFD:function(e){var r=Le.getStream(e);if(!r)throw new Le.ErrnoError(8);return r}};function Ue(e){switch(e){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+e)}}var Be=void 0;function je(e){for(var r="",t=e;U[t];)r+=Be[U[t++]];return r}var $e={},We={},ze={};function He(e){if(void 0===e)return"_unknown";var r=(e=e.replace(/[^a-zA-Z0-9_]/g,"$")).charCodeAt(0);return r>=48&&r<=57?"_"+e:e}function Ge(e,r){return e=He(e),new Function("body","return function "+e+'() {\n "use strict"; return body.apply(this, arguments);\n};\n')(r)}function Ve(e,r){var t=Ge(r,function(e){this.name=r,this.message=e;var t=new Error(e).stack;void 0!==t&&(this.stack=this.toString()+"\n"+t.replace(/^Error(:[^\n]*)?\n/,""))});return t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message},t}var qe=void 0;function Ye(e){throw new qe(e)}var Xe=void 0;function Ke(e){throw new Xe(e)}function Je(e,r,t){function n(r){var n=t(r);n.length!==e.length&&Ke("Mismatched type converter count");for(var o=0;o{We.hasOwnProperty(e)?o[r]=We[e]:(i.push(e),$e.hasOwnProperty(e)||($e[e]=[]),$e[e].push(()=>{o[r]=We[e],++a===i.length&&n(o)}))}),0===i.length&&n(o)}function Qe(e,r,t={}){if(!("argPackAdvance"in r))throw new TypeError("registerType registeredInstance requires argPackAdvance");var n=r.name;if(e||Ye('type "'+n+'" must have a positive integer typeid pointer'),We.hasOwnProperty(e)){if(t.ignoreDuplicateRegistrations)return;Ye("Cannot register type '"+n+"' twice")}if(We[e]=r,delete ze[e],$e.hasOwnProperty(e)){var o=$e[e];delete $e[e],o.forEach(e=>e())}}function Ze(e){if(!(this instanceof br))return!1;if(!(e instanceof br))return!1;for(var r=this.$$.ptrType.registeredClass,t=this.$$.ptr,n=e.$$.ptrType.registeredClass,o=e.$$.ptr;r.baseClass;)t=r.upcast(t),r=r.baseClass;for(;n.baseClass;)o=n.upcast(o),n=n.baseClass;return r===n&&t===o}function er(e){Ye(e.$$.ptrType.registeredClass.name+" instance already deleted")}var rr=!1;function tr(e){}function nr(e){e.count.value-=1,0===e.count.value&&function(e){e.smartPtr?e.smartPtrType.rawDestructor(e.smartPtr):e.ptrType.registeredClass.rawDestructor(e.ptr)}(e)}function or(e,r,t){if(r===t)return e;if(void 0===t.baseClass)return null;var n=or(e,r,t.baseClass);return null===n?null:t.downcast(n)}var ir={};function ar(){return Object.keys(fr).length}function sr(){var e=[];for(var r in fr)fr.hasOwnProperty(r)&&e.push(fr[r]);return e}var lr=[];function ur(){for(;lr.length;){var e=lr.pop();e.$$.deleteScheduled=!1,e.delete()}}var cr=void 0;function dr(e){cr=e,lr.length&&cr&&cr(ur)}var fr={};function pr(e,r){return r=function(e,r){for(void 0===r&&Ye("ptr should not be undefined");e.baseClass;)r=e.upcast(r),e=e.baseClass;return r}(e,r),fr[r]}function mr(e,r){return r.ptrType&&r.ptr||Ke("makeClassHandle requires ptr and ptrType"),!!r.smartPtrType!==!!r.smartPtr&&Ke("Both smartPtrType and smartPtr must be specified"),r.count={value:1},gr(Object.create(e,{$$:{value:r}}))}function hr(e){var r=this.getPointee(e);if(!r)return this.destructor(e),null;var t=pr(this.registeredClass,r);if(void 0!==t){if(0===t.$$.count.value)return t.$$.ptr=r,t.$$.smartPtr=e,t.clone();var n=t.clone();return this.destructor(e),n}function o(){return this.isSmartPointer?mr(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:r,smartPtrType:this,smartPtr:e}):mr(this.registeredClass.instancePrototype,{ptrType:this,ptr:e})}var i,a=this.registeredClass.getActualType(r),s=ir[a];if(!s)return o.call(this);i=this.isConst?s.constPointerType:s.pointerType;var l=or(r,this.registeredClass,i.registeredClass);return null===l?o.call(this):this.isSmartPointer?mr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l,smartPtrType:this,smartPtr:e}):mr(i.registeredClass.instancePrototype,{ptrType:i,ptr:l})}function gr(e){return"undefined"==typeof FinalizationRegistry?(gr=e=>e,e):(rr=new FinalizationRegistry(e=>{console.warn(e.leakWarning.stack.replace(/^Error: /,"")),nr(e.$$)}),gr=e=>{var r=e.$$;if(!!r.smartPtr){var t={$$:r},n=r.ptrType.registeredClass;t.leakWarning=new Error("Embind found a leaked C++ instance "+n.name+" <0x"+r.ptr.toString(16)+">.\nWe'll free it automatically in this case, but this functionality is not reliable across various environments.\nMake sure to invoke .delete() manually once you're done with the instance instead.\nOriginally allocated"),"captureStackTrace"in Error&&Error.captureStackTrace(t.leakWarning,hr),rr.register(e,t,e)}return e},tr=e=>rr.unregister(e),gr(e))}function vr(){if(this.$$.ptr||er(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var e,r=gr(Object.create(Object.getPrototypeOf(this),{$$:{value:(e=this.$$,{count:e.count,deleteScheduled:e.deleteScheduled,preservePointerOnDelete:e.preservePointerOnDelete,ptr:e.ptr,ptrType:e.ptrType,smartPtr:e.smartPtr,smartPtrType:e.smartPtrType})}}));return r.$$.count.value+=1,r.$$.deleteScheduled=!1,r}function yr(){this.$$.ptr||er(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),tr(this),nr(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)}function Er(){return!this.$$.ptr}function wr(){return this.$$.ptr||er(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),lr.push(this),1===lr.length&&cr&&cr(ur),this.$$.deleteScheduled=!0,this}function br(){}function _r(e,r,t){if(void 0===e[r].overloadTable){var n=e[r];e[r]=function(){return e[r].overloadTable.hasOwnProperty(arguments.length)||Ye("Function '"+t+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+e[r].overloadTable+")!"),e[r].overloadTable[arguments.length].apply(this,arguments)},e[r].overloadTable=[],e[r].overloadTable[n.argCount]=n}}function Tr(e,r,t,n,o,i,a,s){this.name=e,this.constructor=r,this.instancePrototype=t,this.rawDestructor=n,this.baseClass=o,this.getActualType=i,this.upcast=a,this.downcast=s,this.pureVirtualFunctions=[]}function kr(e,r,t){for(;r!==t;)r.upcast||Ye("Expected null or instance of "+t.name+", got an instance of "+r.name),e=r.upcast(e),r=r.baseClass;return e}function Sr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name);var t=r.$$.ptrType.registeredClass;return kr(r.$$.ptr,t,this.registeredClass)}function Cr(e,r){var t;if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),this.isSmartPointer?(t=this.rawConstructor(),null!==e&&e.push(this.rawDestructor,t),t):0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),!this.isConst&&r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);var n=r.$$.ptrType.registeredClass;if(t=kr(r.$$.ptr,n,this.registeredClass),this.isSmartPointer)switch(void 0===r.$$.smartPtr&&Ye("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:r.$$.smartPtrType===this?t=r.$$.smartPtr:Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);break;case 1:t=r.$$.smartPtr;break;case 2:if(r.$$.smartPtrType===this)t=r.$$.smartPtr;else{var o=r.clone();t=this.rawShare(t,Yr.toHandle(function(){o.delete()})),null!==e&&e.push(this.rawDestructor,t)}break;default:Ye("Unsupporting sharing policy")}return t}function Pr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+r.$$.ptrType.name+" to parameter type "+this.name);var t=r.$$.ptrType.registeredClass;return kr(r.$$.ptr,t,this.registeredClass)}function Ar(e){return this.fromWireType(W[e>>2])}function Fr(e){return this.rawGetPointee&&(e=this.rawGetPointee(e)),e}function Dr(e){this.rawDestructor&&this.rawDestructor(e)}function Or(e){null!==e&&e.delete()}function Rr(e,r,t,n,o,i,a,s,l,u,c){this.name=e,this.registeredClass=r,this.isReference=t,this.isConst=n,this.isSmartPointer=o,this.pointeeType=i,this.sharingPolicy=a,this.rawGetPointee=s,this.rawConstructor=l,this.rawShare=u,this.rawDestructor=c,o||void 0!==r.baseClass?this.toWireType=Cr:n?(this.toWireType=Sr,this.destructorFunction=null):(this.toWireType=Pr,this.destructorFunction=null)}function Mr(e,t,n){return e.includes("j")?function(e,t,n){F("dynCall_"+e in r,"bad function pointer type - no table for sig '"+e+"'"),n&&n.length?F(n.length===e.substring(1).replace(/j/g,"--").length):F(1==e.length);var o=r["dynCall_"+e];return n&&n.length?o.apply(null,[t].concat(n)):o.call(null,t)}(e,t,n):(F(Ae(t),"missing table entry in dynCall: "+t),Ae(t).apply(null,n))}function Nr(e,r){var t=(e=je(e)).includes("j")?function(e,r){F(e.includes("j"),"getDynCaller should only be called with i64 sigs");var t=[];return function(){return t.length=0,Object.assign(t,arguments),Mr(e,r,t)}}(e,r):Ae(r);return"function"!=typeof t&&Ye("unknown function pointer with signature "+e+": "+r),t}var Ir=void 0;function Lr(e){var r=_t(e),t=je(r);return yt(r),t}function xr(e,r){var t=[],n={};throw r.forEach(function e(r){n[r]||We[r]||(ze[r]?ze[r].forEach(e):(t.push(r),n[r]=!0))}),new Ir(e+": "+t.map(Lr).join([", "]))}function Ur(e,r){for(var t=[],n=0;n>2)+n]);return t}function Br(e){for(;e.length;){var r=e.pop();e.pop()(r)}}function jr(e,r){if(!(e instanceof Function))throw new TypeError("new_ called with constructor type "+typeof e+" which is not a function");var t=Ge(e.name||"unknownFunctionName",function(){});t.prototype=e.prototype;var n=new t,o=e.apply(n,r);return o instanceof Object?o:n}function $r(e,r,t,n,o){var i=r.length;i<2&&Ye("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var a=null!==r[1]&&null!==t,s=!1,l=1;l0?", ":"")+d),f+=(u?"var rv = ":"")+"invoker(fn"+(d.length>0?", ":"")+d+");\n",s)f+="runDestructors(destructors);\n";else for(l=a?1:2;l4&&0===--Hr[e].refcount&&(Hr[e]=void 0,zr.push(e))}function Vr(){for(var e=0,r=5;r(e||Ye("Cannot use deleted val. handle = "+e),Hr[e].value),toHandle:e=>{switch(e){case void 0:return 1;case null:return 2;case!0:return 3;case!1:return 4;default:var r=zr.length?zr.pop():Hr.length;return Hr[r]={refcount:1,value:e},r}}};function Xr(e){if(null===e)return"null";var r=typeof e;return"object"===r||"array"===r||"function"===r?e.toString():""+e}function Kr(e,r){switch(r){case 2:return function(e){return this.fromWireType(z[e>>2])};case 3:return function(e){return this.fromWireType(H[e>>3])};default:throw new TypeError("Unknown float type: "+e)}}function Jr(e,r,t){switch(r){case 0:return t?function(e){return x[e]}:function(e){return U[e]};case 1:return t?function(e){return B[e>>1]}:function(e){return j[e>>1]};case 2:return t?function(e){return $[e>>2]}:function(e){return W[e>>2]};default:throw new TypeError("Unknown integer type: "+e)}}function Qr(e,r){var t=We[e];return void 0===t&&Ye(r+" has unknown type "+Lr(e)),t}var Zr={};var et=[];var rt=[];function tt(e){return e<0||0===e&&1/e==-1/0}function nt(e,r){return F(r===(0|r)),(e>>>0)+4294967296*r}function ot(e,r){return(e>>>0)+4294967296*(r>>>0)}function it(e,r){if(e<=0)return e;var t=r<=32?Math.abs(1<=t&&(r<=32||e>t)&&(e=-2*t+e),e}function at(e,r){return e>=0?e:r<=32?2*Math.abs(1<>3]),n+=8):"i64"==e?(r=[$[n>>2],$[n+4>>2]],n+=8):(F(!(3&n)),e="i32",r=$[n>>2],n+=4),r}for(var i,a,s,l=[];;){var u=t;if(0===(i=x[t|0]))break;if(a=x[t+1|0],37==i){var c=!1,d=!1,f=!1,p=!1,m=!1;e:for(;;){switch(a){case 43:c=!0;break;case 45:d=!0;break;case 35:f=!0;break;case 48:if(p)break e;p=!0;break;case 32:m=!0;break;default:break e}t++,a=x[t+1|0]}var h=0;if(42==a)h=o("i32"),t++,a=x[t+1|0];else for(;a>=48&&a<=57;)h=10*h+(a-48),t++,a=x[t+1|0];var g,v=!1,y=-1;if(46==a){if(y=0,v=!0,t++,42==(a=x[t+1|0]))y=o("i32"),t++;else for(;;){var E=x[t+1|0];if(E<48||E>57)break;y=10*y+(E-48),t++}a=x[t+1|0]}switch(y<0&&(y=6,v=!1),String.fromCharCode(a)){case"h":104==x[t+2|0]?(t++,g=1):g=2;break;case"l":108==x[t+2|0]?(t++,g=8):g=4;break;case"L":case"q":case"j":g=8;break;case"z":case"t":case"I":g=4;break;default:g=null}switch(g&&t++,a=x[t+1|0],String.fromCharCode(a)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":var w=100==a||105==a;if(s=o("i"+8*(g=g||4)),8==g&&(s=117==a?ot(s[0],s[1]):nt(s[0],s[1])),g<=4)s=(w?it:at)(s&Math.pow(256,g)-1,8*g);var b=Math.abs(s),_="";if(100==a||105==a)S=it(s,8*g).toString(10);else if(117==a)S=at(s,8*g).toString(10),s=Math.abs(s);else if(111==a)S=(f?"0":"")+b.toString(8);else if(120==a||88==a){if(_=f&&0!=s?"0x":"",s<0){s=-s,S=(b-1).toString(16);for(var T=[],k=0;k=0&&(c?_="+"+_:m&&(_=" "+_)),"-"==S.charAt(0)&&(_="-"+_,S=S.substr(1));_.length+S.lengthA&&A>=-4?(a=(103==a?"f":"F").charCodeAt(0),y-=A+1):(a=(103==a?"e":"E").charCodeAt(0),y--),P=Math.min(y,20)}101==a||69==a?(S=s.toExponential(P),/[eE][-+]\d$/.test(S)&&(S=S.slice(0,-1)+"0"+S.slice(-1))):102!=a&&70!=a||(S=s.toFixed(P),0===s&&tt(s)&&(S="-"+S));var D=S.split("e");if(C&&!f)for(;D[0].length>1&&D[0].includes(".")&&("0"==D[0].slice(-1)||"."==D[0].slice(-1));)D[0]=D[0].slice(0,-1);else for(f&&-1==S.indexOf(".")&&(D[0]+=".");y>P++;)D[0]+="0";S=D[0]+(D.length>1?"e"+D[1]:""),69==a&&(S=S.toUpperCase()),s>=0&&(c?S="+"+S:m&&(S=" "+S))}else S=(s<0?"-":"")+"inf",p=!1;for(;S.length0;)l.push(32);d||l.push(o("i8"));break;case"n":var M=o("i32*");$[M>>2]=l.length;break;case"%":l.push(i);break;default:for(k=u;k=4)){r+=d+"\n";continue}f=g[1],p=g[2],m=g[3],h=0|g[4]}var v=!1;if(8&e){var y=emscripten_source_map.originalPositionFor({line:m,column:h});(v=y&&y.source)&&(64&e&&(y.source=y.source.substring(y.source.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=" at "+f+" ("+y.source+":"+y.line+":"+y.column+")\n")}(16&e||!v)&&(64&e&&(p=p.substring(p.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=(v?" = "+f:" at "+f)+" ("+p+":"+m+":"+h+")\n"),128&e&&i[0]&&(i[1]==f&&i[2].length>0&&(r=r.replace(/\s+$/,""),r+=" with values: "+i[1]+i[2]+"\n"),i=lt(i[0]))}return r=r.replace(/\s+$/,"")}function ct(e){try{return w.grow(e-L.byteLength+65535>>>16),Z(w.buffer),1}catch(r){_("emscripten_realloc_buffer: Attempted to grow heap from "+L.byteLength+" bytes to "+e+" bytes, but got error: "+r)}}var dt={};function ft(){if(!ft.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:n||"./this.program"};for(var r in dt)void 0===dt[r]?delete e[r]:e[r]=dt[r];var t=[];for(var r in e)t.push(r+"="+e[r]);ft.strings=t}return ft.strings}var pt=function(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=Le.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n},mt=365,ht=146;function gt(e,r,t){var n=t>0?t:I(e)+1,o=new Array(n),i=M(e,o,0,o.length);return r&&(o.length=i),o}Object.defineProperties(pt.prototype,{read:{get:function(){return(this.mode&mt)===mt},set:function(e){e?this.mode|=mt:this.mode&=-366}},write:{get:function(){return(this.mode&ht)===ht},set:function(e){e?this.mode|=ht:this.mode&=-147}},isFolder:{get:function(){return Le.isDir(this.mode)}},isDevice:{get:function(){return Le.isChrdev(this.mode)}}}),Le.FSNode=pt,Le.staticInit(),Ie={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135},function(){for(var e=new Array(256),r=0;r<256;++r)e[r]=String.fromCharCode(r);Be=e}(),qe=r.BindingError=Ve(Error,"BindingError"),Xe=r.InternalError=Ve(Error,"InternalError"),br.prototype.isAliasOf=Ze,br.prototype.clone=vr,br.prototype.delete=yr,br.prototype.isDeleted=Er,br.prototype.deleteLater=wr,r.getInheritedInstanceCount=ar,r.getLiveInheritedInstances=sr,r.flushPendingDeletes=ur,r.setDelayFunction=dr,Rr.prototype.getPointee=Fr,Rr.prototype.destructor=Dr,Rr.prototype.argPackAdvance=8,Rr.prototype.readValueFromPointer=Ar,Rr.prototype.deleteObject=Or,Rr.prototype.fromWireType=hr,Ir=r.UnboundTypeError=Ve(Error,"UnboundTypeError"),r.count_emval_handles=Vr,r.get_first_emval=qr;var vt={__syscall_fcntl64:function(e,r,t){xe.varargs=t;try{var n=xe.getStreamFromFD(e);switch(r){case 0:return(o=xe.get())<0?-28:Le.createStream(n,o).fd;case 1:case 2:case 6:case 7:return 0;case 3:return n.flags;case 4:var o=xe.get();return n.flags|=o,0;case 5:o=xe.get();return B[o+0>>1]=2,0;case 16:case 8:default:return-28;case 9:return i=28,$[bt()>>2]=i,-1}}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return-e.errno}var i},__syscall_openat:function(e,r,t,n){xe.varargs=n;try{r=xe.getStr(r),r=xe.calculateAt(e,r);var o=n?xe.get():0;return Le.open(r,t,o).fd}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return-e.errno}},_embind_register_bigint:function(e,r,t,n,o){},_embind_register_bool:function(e,r,t,n,o){var i=Ue(t);Qe(e,{name:r=je(r),fromWireType:function(e){return!!e},toWireType:function(e,r){return r?n:o},argPackAdvance:8,readValueFromPointer:function(e){var n;if(1===t)n=x;else if(2===t)n=B;else{if(4!==t)throw new TypeError("Unknown boolean type size: "+r);n=$}return this.fromWireType(n[e>>i])},destructorFunction:null})},_embind_register_class:function(e,t,n,o,i,a,s,l,u,c,d,f,p){d=je(d),a=Nr(i,a),l&&(l=Nr(s,l)),c&&(c=Nr(u,c)),p=Nr(f,p);var m=He(d);!function(e,t,n){r.hasOwnProperty(e)?((void 0===n||void 0!==r[e].overloadTable&&void 0!==r[e].overloadTable[n])&&Ye("Cannot register public name '"+e+"' twice"),_r(r,e,e),r.hasOwnProperty(n)&&Ye("Cannot register multiple overloads of a function with the same number of arguments ("+n+")!"),r[e].overloadTable[n]=t):(r[e]=t,void 0!==n&&(r[e].numArguments=n))}(m,function(){xr("Cannot construct "+d+" due to unbound types",[o])}),Je([e,t,n],o?[o]:[],function(t){var n,i;t=t[0],i=o?(n=t.registeredClass).instancePrototype:br.prototype;var s=Ge(m,function(){if(Object.getPrototypeOf(this)!==u)throw new qe("Use 'new' to construct "+d);if(void 0===f.constructor_body)throw new qe(d+" has no accessible constructor");var e=f.constructor_body[arguments.length];if(void 0===e)throw new qe("Tried to invoke ctor of "+d+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(f.constructor_body).toString()+") parameters instead!");return e.apply(this,arguments)}),u=Object.create(i,{constructor:{value:s}});s.prototype=u;var f=new Tr(d,s,u,p,n,a,l,c),h=new Rr(d,f,!0,!1,!1),g=new Rr(d+"*",f,!1,!1,!1),v=new Rr(d+" const*",f,!1,!0,!1);return ir[e]={pointerType:g,constPointerType:v},function(e,t,n){r.hasOwnProperty(e)||Ke("Replacing nonexistant public symbol"),void 0!==r[e].overloadTable&&void 0!==n?r[e].overloadTable[n]=t:(r[e]=t,r[e].argCount=n)}(m,s),[h,g,v]})},_embind_register_class_constructor:function(e,r,t,n,o,i){F(r>0);var a=Ur(r,t);o=Nr(n,o),Je([],[e],function(e){var t="constructor "+(e=e[0]).name;if(void 0===e.registeredClass.constructor_body&&(e.registeredClass.constructor_body=[]),void 0!==e.registeredClass.constructor_body[r-1])throw new qe("Cannot register multiple constructors with identical number of parameters ("+(r-1)+") for class '"+e.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!");return e.registeredClass.constructor_body[r-1]=()=>{xr("Cannot construct "+e.name+" due to unbound types",a)},Je([],a,function(n){return n.splice(1,0,null),e.registeredClass.constructor_body[r-1]=$r(t,n,null,o,i),[]}),[]})},_embind_register_class_function:function(e,r,t,n,o,i,a,s){var l=Ur(t,n);r=je(r),i=Nr(o,i),Je([],[e],function(e){var n=(e=e[0]).name+"."+r;function o(){xr("Cannot call "+n+" due to unbound types",l)}r.startsWith("@@")&&(r=Symbol[r.substring(2)]),s&&e.registeredClass.pureVirtualFunctions.push(r);var u=e.registeredClass.instancePrototype,c=u[r];return void 0===c||void 0===c.overloadTable&&c.className!==e.name&&c.argCount===t-2?(o.argCount=t-2,o.className=e.name,u[r]=o):(_r(u,r,n),u[r].overloadTable[t-2]=o),Je([],l,function(o){var s=$r(n,o,e,i,a);return void 0===u[r].overloadTable?(s.argCount=t-2,u[r]=s):u[r].overloadTable[t-2]=s,[]}),[]})},_embind_register_class_property:function(e,r,t,n,o,i,a,s,l,u){r=je(r),o=Nr(n,o),Je([],[e],function(e){var n=(e=e[0]).name+"."+r,c={get:function(){xr("Cannot access "+n+" due to unbound types",[t,a])},enumerable:!0,configurable:!0};return c.set=l?()=>{xr("Cannot access "+n+" due to unbound types",[t,a])}:e=>{Ye(n+" is a read-only property")},Object.defineProperty(e.registeredClass.instancePrototype,r,c),Je([],l?[t,a]:[t],function(t){var a=t[0],c={get:function(){var r=Wr(this,e,n+" getter");return a.fromWireType(o(i,r))},enumerable:!0};if(l){l=Nr(s,l);var d=t[1];c.set=function(r){var t=Wr(this,e,n+" setter"),o=[];l(u,t,d.toWireType(o,r)),Br(o)}}return Object.defineProperty(e.registeredClass.instancePrototype,r,c),[]}),[]})},_embind_register_emval:function(e,r){Qe(e,{name:r=je(r),fromWireType:function(e){var r=Yr.toValue(e);return Gr(e),r},toWireType:function(e,r){return Yr.toHandle(r)},argPackAdvance:8,readValueFromPointer:Ar,destructorFunction:null})},_embind_register_float:function(e,r,t){var n=Ue(t);Qe(e,{name:r=je(r),fromWireType:function(e){return e},toWireType:function(e,r){if("number"!=typeof r&&"boolean"!=typeof r)throw new TypeError('Cannot convert "'+Xr(r)+'" to '+this.name);return r},argPackAdvance:8,readValueFromPointer:Kr(r,n),destructorFunction:null})},_embind_register_integer:function(e,r,t,n,o){r=je(r),-1===o&&(o=4294967295);var i=Ue(t),a=e=>e;if(0===n){var s=32-8*t;a=e=>e<>>s}var l=r.includes("unsigned"),u=(e,t)=>{if("number"!=typeof e&&"boolean"!=typeof e)throw new TypeError('Cannot convert "'+Xr(e)+'" to '+t);if(eo)throw new TypeError('Passing a number "'+Xr(e)+'" from JS side to C/C++ side to an argument of type "'+r+'", which is outside the valid range ['+n+", "+o+"]!")};Qe(e,{name:r,fromWireType:a,toWireType:l?function(e,r){return u(r,this.name),r>>>0}:function(e,r){return u(r,this.name),r},argPackAdvance:8,readValueFromPointer:Jr(r,i,0!==n),destructorFunction:null})},_embind_register_memory_view:function(e,r,t){var n=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][r];function o(e){var r=W,t=r[e>>=2],o=r[e+1];return new n(L,o,t)}Qe(e,{name:t=je(t),fromWireType:o,argPackAdvance:8,readValueFromPointer:o},{ignoreDuplicateRegistrations:!0})},_embind_register_std_string:function(e,r){var t="std::string"===(r=je(r));Qe(e,{name:r,fromWireType:function(e){var r,n=W[e>>2];if(t)for(var o=e+4,i=0;i<=n;++i){var a=e+4+i;if(i==n||0==U[a]){var s=R(o,a-o);void 0===r?r=s:(r+=String.fromCharCode(0),r+=s),o=a+1}}else{var l=new Array(n);for(i=0;iI(r):()=>r.length)(),i=Et(4+o+1);if(W[i>>2]=o,t&&n)N(r,i+4,o+1);else if(n)for(var a=0;a255&&(yt(i),Ye("String has UTF-16 code units that do not fit in 8 bits")),U[i+4+a]=s}else for(a=0;aj,s=1):4===r&&(n=X,o=K,a=J,i=()=>W,s=2),Qe(e,{name:t,fromWireType:function(e){for(var t,o=W[e>>2],a=i(),l=e+4,u=0;u<=o;++u){var c=e+4+u*r;if(u==o||0==a[c>>s]){var d=n(l,c-l);void 0===t?t=d:(t+=String.fromCharCode(0),t+=d),l=c+r}}return yt(e),t},toWireType:function(e,n){"string"!=typeof n&&Ye("Cannot pass non-string to C++ string type "+t);var i=a(n),l=Et(4+i+r);return W[l>>2]=i>>s,o(n,l+4,i+r),null!==e&&e.push(yt,l),l},argPackAdvance:8,readValueFromPointer:Ar,destructorFunction:function(e){yt(e)}})},_embind_register_void:function(e,r){Qe(e,{isVoid:!0,name:r=je(r),argPackAdvance:0,fromWireType:function(){},toWireType:function(e,r){}})},_emscripten_date_now:function(){return Date.now()},_emval_as:function(e,r,t){e=Yr.toValue(e),r=Qr(r,"emval::as");var n=[],o=Yr.toHandle(n);return $[t>>2]=o,r.toWireType(n,e)},_emval_call_void_method:function(e,r,t,n){var o,i;(e=et[e])(r=Yr.toValue(r),t=void 0===(i=Zr[o=t])?je(o):i,null,n)},_emval_decref:Gr,_emval_get_method_caller:function(e,r){var t=function(e,r){for(var t=new Array(e),n=0;n>2)+n],"parameter "+n);return t}(e,r),n=t[0],o=n.name+"_$"+t.slice(1).map(function(e){return e.name}).join("_")+"$",i=rt[o];if(void 0!==i)return i;for(var a=["retType"],s=[n],l="",u=0;u4&&(Hr[e].refcount+=1)},_emval_run_destructors:function(e){Br(Yr.toValue(e)),Gr(e)},_emval_take_value:function(e,r){var t=(e=Qr(e,"_emval_take_value")).readValueFromPointer(r);return Yr.toHandle(t)},_gmtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getUTCSeconds(),$[r+4>>2]=t.getUTCMinutes(),$[r+8>>2]=t.getUTCHours(),$[r+12>>2]=t.getUTCDate(),$[r+16>>2]=t.getUTCMonth(),$[r+20>>2]=t.getUTCFullYear()-1900,$[r+24>>2]=t.getUTCDay();var n=Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0),o=(t.getTime()-n)/864e5|0;$[r+28>>2]=o},_localtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getSeconds(),$[r+4>>2]=t.getMinutes(),$[r+8>>2]=t.getHours(),$[r+12>>2]=t.getDate(),$[r+16>>2]=t.getMonth(),$[r+20>>2]=t.getFullYear()-1900,$[r+24>>2]=t.getDay();var n=new Date(t.getFullYear(),0,1),o=(t.getTime()-n.getTime())/864e5|0;$[r+28>>2]=o,$[r+36>>2]=-60*t.getTimezoneOffset();var i=new Date(t.getFullYear(),6,1).getTimezoneOffset(),a=n.getTimezoneOffset(),s=0|(i!=a&&t.getTimezoneOffset()==Math.min(a,i));$[r+32>>2]=s},_mktime_js:function(e){var r=new Date($[e+20>>2]+1900,$[e+16>>2],$[e+12>>2],$[e+8>>2],$[e+4>>2],$[e>>2],0),t=$[e+32>>2],n=r.getTimezoneOffset(),o=new Date(r.getFullYear(),0,1),i=new Date(r.getFullYear(),6,1).getTimezoneOffset(),a=o.getTimezoneOffset(),s=Math.min(a,i);if(t<0)$[e+32>>2]=Number(i!=a&&s==n);else if(t>0!=(s==n)){var l=Math.max(a,i),u=t>0?s:l;r.setTime(r.getTime()+6e4*(u-n))}$[e+24>>2]=r.getDay();var c=(r.getTime()-o.getTime())/864e5|0;return $[e+28>>2]=c,$[e>>2]=r.getSeconds(),$[e+4>>2]=r.getMinutes(),$[e+8>>2]=r.getHours(),$[e+12>>2]=r.getDate(),$[e+16>>2]=r.getMonth(),r.getTime()/1e3|0},_tzset_js:function e(r,t,n){e.called||(e.called=!0,function(e,r,t){var n=(new Date).getFullYear(),o=new Date(n,0,1),i=new Date(n,6,1),a=o.getTimezoneOffset(),s=i.getTimezoneOffset(),l=Math.max(a,s);function u(e){var r=e.toTimeString().match(/\(([A-Za-z ]+)\)$/);return r?r[1]:"GMT"}$[e>>2]=60*l,$[r>>2]=Number(a!=s);var c=u(o),d=u(i),f=Q(c),p=Q(d);s>2]=f,$[t+4>>2]=p):($[t>>2]=p,$[t+4>>2]=f)}(r,t,n))},abort:function(){ye("native code called abort()")},emscripten_log:function(e,r,t){!function(e,r){24&e&&(r=r.replace(/\s+$/,""),r+=(r.length>0?"\n":"")+ut(e)),1&e?4&e?console.error(r):2&e?console.warn(r):512&e?console.info(r):256&e?console.debug(r):console.log(r):6&e?_(r):b(r)}(e,O(st(r,t),0))},emscripten_resize_heap:function(e){var r=U.length;F((e>>>=0)>r);var t=2147483648;if(e>t)return _("Cannot enlarge memory, asked to go up to "+e+" bytes, but the limit is "+t+" bytes!"),!1;let n=(e,r)=>e+(r-e%r)%r;for(var o=1;o<=4;o*=2){var i=r*(1+.2/o);i=Math.min(i,e+100663296);var a=Math.min(t,n(Math.max(e,i),65536));if(ct(a))return!0}return _("Failed to grow the heap from "+r+" bytes to "+a+" bytes, not enough memory!"),!1},environ_get:function(e,r){var t=0;return ft().forEach(function(n,o){var i=r+t;$[e+4*o>>2]=i,function(e,r,t){for(var n=0;n>2]=t.length;var n=0;return t.forEach(function(e){n+=e.length+1}),$[r>>2]=n,0},fd_close:function(e){try{var r=xe.getStreamFromFD(e);return Le.close(r),0}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return e.errno}},fd_fdstat_get:function(e,r){try{var t=xe.getStreamFromFD(e),n=t.tty?2:Le.isDir(t.mode)?3:Le.isLink(t.mode)?7:4;return x[r|0]=n,0}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return e.errno}},fd_read:function(e,r,t,n){try{var o=xe.getStreamFromFD(e),i=xe.doReadv(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return e.errno}},fd_seek:function(e,r,t,n,o){try{var i=xe.getStreamFromFD(e),a=4294967296*t+(r>>>0),s=9007199254740992;return a<=-s||a>=s?-61:(Le.llseek(i,a,n),be=[i.position>>>0,(we=i.position,+Math.abs(we)>=1?we>0?(0|Math.min(+Math.floor(we/4294967296),4294967295))>>>0:~~+Math.ceil((we-+(~~we>>>0))/4294967296)>>>0:0)],$[o>>2]=be[0],$[o+4>>2]=be[1],i.getdents&&0===a&&0===n&&(i.getdents=null),0)}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return e.errno}},fd_write:function(e,r,t,n){try{var o=xe.getStreamFromFD(e),i=xe.doWritev(o,r,t);return $[n>>2]=i,0}catch(e){if(void 0===Le||!(e instanceof Le.ErrnoError))throw e;return e.errno}},setTempRet0:function(e){}};!function(){var e={env:vt,wasi_snapshot_preview1:vt};function t(e,t){var n,o=e.exports;r.asm=o,F(w=r.asm.memory,"memory not found in wasm exports"),Z(w.buffer),F(re=r.asm.__indirect_function_table,"table not found in wasm exports"),n=r.asm.__wasm_call_ctors,ae.unshift(n),ve("wasm-instantiate")}ge("wasm-instantiate");var n=r;function o(e){F(r===n,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"),n=null,t(e.instance)}function i(r){return function(){if(!E&&(s||l)){if("function"==typeof fetch&&!Te(Ee))return fetch(Ee,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+Ee+"'";return e.arrayBuffer()}).catch(function(){return Se(Ee)});if(f)return new Promise(function(e,r){f(Ee,function(r){e(new Uint8Array(r))},r)})}return Promise.resolve().then(function(){return Se(Ee)})}().then(function(r){return WebAssembly.instantiate(r,e)}).then(function(e){return e}).then(r,function(e){_("failed to asynchronously prepare wasm: "+e),Te(Ee)&&_("warning: Loading from a file URI ("+Ee+") is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing"),ye(e)})}if(r.instantiateWasm)try{return r.instantiateWasm(e,t)}catch(e){return _("Module.instantiateWasm callback failed with error: "+e),!1}E||"function"!=typeof WebAssembly.instantiateStreaming||_e(Ee)||Te(Ee)||"function"!=typeof fetch?i(o):fetch(Ee,{credentials:"same-origin"}).then(function(r){return WebAssembly.instantiateStreaming(r,e).then(o,function(e){return _("wasm streaming compile failed: "+e),_("falling back to ArrayBuffer instantiation"),i(o)})})}(),r.___wasm_call_ctors=ke("__wasm_call_ctors");var yt=r._free=ke("free"),Et=r._malloc=ke("malloc"),wt=r._strlen=ke("strlen"),bt=r.___errno_location=ke("__errno_location"),_t=r.___getTypeName=ke("__getTypeName");r.___embind_register_native_and_builtin_types=ke("__embind_register_native_and_builtin_types");var Tt=r.___stdio_exit=ke("__stdio_exit"),kt=r._emscripten_builtin_memalign=ke("emscripten_builtin_memalign"),St=r._emscripten_stack_init=function(){return(St=r._emscripten_stack_init=r.asm.emscripten_stack_init).apply(null,arguments)};r._emscripten_stack_get_free=function(){return(r._emscripten_stack_get_free=r.asm.emscripten_stack_get_free).apply(null,arguments)},r._emscripten_stack_get_base=function(){return(r._emscripten_stack_get_base=r.asm.emscripten_stack_get_base).apply(null,arguments)};var Ct,Pt=r._emscripten_stack_get_end=function(){return(Pt=r._emscripten_stack_get_end=r.asm.emscripten_stack_get_end).apply(null,arguments)};function At(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function Ft(e){function t(){Ct||(Ct=!0,r.calledRun=!0,A||(oe(),F(!le),le=!0,r.noFSInit||Le.init.initialized||Le.init(),Le.ignorePermissions=!1,Ce(ae),r.onRuntimeInitialized&&r.onRuntimeInitialized(),F(!r._main,'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'),function(){if(oe(),r.postRun)for("function"==typeof r.postRun&&(r.postRun=[r.postRun]);r.postRun.length;)ce(r.postRun.shift());Ce(se)}()))}de>0||(St(),ne(),function(){if(r.preRun)for("function"==typeof r.preRun&&(r.preRun=[r.preRun]);r.preRun.length;)ue(r.preRun.shift());Ce(ie)}(),de>0||(r.setStatus?(r.setStatus("Running..."),setTimeout(function(){setTimeout(function(){r.setStatus("")},1),t()},1)):t(),oe()))}if(r.stackSave=ke("stackSave"),r.stackRestore=ke("stackRestore"),r.stackAlloc=ke("stackAlloc"),r.dynCall_ijiii=ke("dynCall_ijiii"),r.dynCall_viiijj=ke("dynCall_viiijj"),r.dynCall_jij=ke("dynCall_jij"),r.dynCall_jii=ke("dynCall_jii"),r.dynCall_jiji=ke("dynCall_jiji"),r._ff_h264_cabac_tables=112940,P("intArrayFromString",!1),P("intArrayToString",!1),P("ccall",!1),P("cwrap",!1),P("setValue",!1),P("getValue",!1),P("allocate",!1),P("UTF8ArrayToString",!1),P("UTF8ToString",!1),P("stringToUTF8Array",!1),P("stringToUTF8",!1),P("lengthBytesUTF8",!1),P("stackTrace",!1),P("addOnPreRun",!1),P("addOnInit",!1),P("addOnPreMain",!1),P("addOnExit",!1),P("addOnPostRun",!1),P("writeStringToMemory",!1),P("writeArrayToMemory",!1),P("writeAsciiToMemory",!1),P("addRunDependency",!0),P("removeRunDependency",!0),P("FS_createFolder",!1),P("FS_createPath",!0),P("FS_createDataFile",!0),P("FS_createPreloadedFile",!0),P("FS_createLazyFile",!0),P("FS_createLink",!1),P("FS_createDevice",!0),P("FS_unlink",!0),P("getLEB",!1),P("getFunctionTables",!1),P("alignFunctionTables",!1),P("registerFunctions",!1),P("addFunction",!1),P("removeFunction",!1),P("prettyPrint",!1),P("dynCall",!1),P("getCompilerSetting",!1),P("print",!1),P("printErr",!1),P("getTempRet0",!1),P("setTempRet0",!1),P("callMain",!1),P("abort",!1),P("keepRuntimeAlive",!1),P("ptrToString",!1),P("zeroMemory",!1),P("stringToNewUTF8",!1),P("emscripten_realloc_buffer",!1),P("ENV",!1),P("ERRNO_CODES",!1),P("ERRNO_MESSAGES",!1),P("setErrNo",!1),P("inetPton4",!1),P("inetNtop4",!1),P("inetPton6",!1),P("inetNtop6",!1),P("readSockaddr",!1),P("writeSockaddr",!1),P("DNS",!1),P("getHostByName",!1),P("Protocols",!1),P("Sockets",!1),P("getRandomDevice",!1),P("traverseStack",!1),P("UNWIND_CACHE",!1),P("convertPCtoSourceLocation",!1),P("readAsmConstArgsArray",!1),P("readAsmConstArgs",!1),P("mainThreadEM_ASM",!1),P("jstoi_q",!1),P("jstoi_s",!1),P("getExecutableName",!1),P("listenOnce",!1),P("autoResumeAudioContext",!1),P("dynCallLegacy",!1),P("getDynCaller",!1),P("dynCall",!1),P("setWasmTableEntry",!1),P("getWasmTableEntry",!1),P("handleException",!1),P("runtimeKeepalivePush",!1),P("runtimeKeepalivePop",!1),P("callUserCallback",!1),P("maybeExit",!1),P("safeSetTimeout",!1),P("asmjsMangle",!1),P("asyncLoad",!1),P("alignMemory",!1),P("mmapAlloc",!1),P("reallyNegative",!1),P("unSign",!1),P("reSign",!1),P("formatString",!1),P("PATH",!1),P("PATH_FS",!1),P("SYSCALLS",!1),P("getSocketFromFD",!1),P("getSocketAddress",!1),P("JSEvents",!1),P("registerKeyEventCallback",!1),P("specialHTMLTargets",!1),P("maybeCStringToJsString",!1),P("findEventTarget",!1),P("findCanvasEventTarget",!1),P("getBoundingClientRect",!1),P("fillMouseEventData",!1),P("registerMouseEventCallback",!1),P("registerWheelEventCallback",!1),P("registerUiEventCallback",!1),P("registerFocusEventCallback",!1),P("fillDeviceOrientationEventData",!1),P("registerDeviceOrientationEventCallback",!1),P("fillDeviceMotionEventData",!1),P("registerDeviceMotionEventCallback",!1),P("screenOrientation",!1),P("fillOrientationChangeEventData",!1),P("registerOrientationChangeEventCallback",!1),P("fillFullscreenChangeEventData",!1),P("registerFullscreenChangeEventCallback",!1),P("registerRestoreOldStyle",!1),P("hideEverythingExceptGivenElement",!1),P("restoreHiddenElements",!1),P("setLetterbox",!1),P("currentFullscreenStrategy",!1),P("restoreOldWindowedStyle",!1),P("softFullscreenResizeWebGLRenderTarget",!1),P("doRequestFullscreen",!1),P("fillPointerlockChangeEventData",!1),P("registerPointerlockChangeEventCallback",!1),P("registerPointerlockErrorEventCallback",!1),P("requestPointerLock",!1),P("fillVisibilityChangeEventData",!1),P("registerVisibilityChangeEventCallback",!1),P("registerTouchEventCallback",!1),P("fillGamepadEventData",!1),P("registerGamepadEventCallback",!1),P("registerBeforeUnloadEventCallback",!1),P("fillBatteryEventData",!1),P("battery",!1),P("registerBatteryEventCallback",!1),P("setCanvasElementSize",!1),P("getCanvasElementSize",!1),P("demangle",!1),P("demangleAll",!1),P("jsStackTrace",!1),P("stackTrace",!1),P("getEnvStrings",!1),P("checkWasiClock",!1),P("writeI53ToI64",!1),P("writeI53ToI64Clamped",!1),P("writeI53ToI64Signaling",!1),P("writeI53ToU64Clamped",!1),P("writeI53ToU64Signaling",!1),P("readI53FromI64",!1),P("readI53FromU64",!1),P("convertI32PairToI53",!1),P("convertU32PairToI53",!1),P("dlopenMissingError",!1),P("setImmediateWrapped",!1),P("clearImmediateWrapped",!1),P("polyfillSetImmediate",!1),P("uncaughtExceptionCount",!1),P("exceptionLast",!1),P("exceptionCaught",!1),P("ExceptionInfo",!1),P("exception_addRef",!1),P("exception_decRef",!1),P("Browser",!1),P("setMainLoop",!1),P("wget",!1),P("FS",!1),P("MEMFS",!1),P("TTY",!1),P("PIPEFS",!1),P("SOCKFS",!1),P("_setNetworkCallback",!1),P("tempFixedLengthArray",!1),P("miniTempWebGLFloatBuffers",!1),P("heapObjectForWebGLType",!1),P("heapAccessShiftForWebGLHeap",!1),P("GL",!1),P("emscriptenWebGLGet",!1),P("computeUnpackAlignedImageSize",!1),P("emscriptenWebGLGetTexPixelData",!1),P("emscriptenWebGLGetUniform",!1),P("webglGetUniformLocation",!1),P("webglPrepareUniformLocationsBeforeFirstUse",!1),P("webglGetLeftBracePos",!1),P("emscriptenWebGLGetVertexAttrib",!1),P("writeGLArray",!1),P("AL",!1),P("SDL_unicode",!1),P("SDL_ttfContext",!1),P("SDL_audio",!1),P("SDL",!1),P("SDL_gfx",!1),P("GLUT",!1),P("EGL",!1),P("GLFW_Window",!1),P("GLFW",!1),P("GLEW",!1),P("IDBStore",!1),P("runAndAbortIfError",!1),P("InternalError",!1),P("BindingError",!1),P("UnboundTypeError",!1),P("PureVirtualError",!1),P("init_embind",!1),P("throwInternalError",!1),P("throwBindingError",!1),P("throwUnboundTypeError",!1),P("ensureOverloadTable",!1),P("exposePublicSymbol",!1),P("replacePublicSymbol",!1),P("extendError",!1),P("createNamedFunction",!1),P("registeredInstances",!1),P("getBasestPointer",!1),P("registerInheritedInstance",!1),P("unregisterInheritedInstance",!1),P("getInheritedInstance",!1),P("getInheritedInstanceCount",!1),P("getLiveInheritedInstances",!1),P("registeredTypes",!1),P("awaitingDependencies",!1),P("typeDependencies",!1),P("registeredPointers",!1),P("registerType",!1),P("whenDependentTypesAreResolved",!1),P("embind_charCodes",!1),P("embind_init_charCodes",!1),P("readLatin1String",!1),P("getTypeName",!1),P("heap32VectorToArray",!1),P("requireRegisteredType",!1),P("getShiftFromSize",!1),P("integerReadValueFromPointer",!1),P("enumReadValueFromPointer",!1),P("floatReadValueFromPointer",!1),P("simpleReadValueFromPointer",!1),P("runDestructors",!1),P("new_",!1),P("craftInvokerFunction",!1),P("embind__requireFunction",!1),P("tupleRegistrations",!1),P("structRegistrations",!1),P("genericPointerToWireType",!1),P("constNoSmartPtrRawPointerToWireType",!1),P("nonConstNoSmartPtrRawPointerToWireType",!1),P("init_RegisteredPointer",!1),P("RegisteredPointer",!1),P("RegisteredPointer_getPointee",!1),P("RegisteredPointer_destructor",!1),P("RegisteredPointer_deleteObject",!1),P("RegisteredPointer_fromWireType",!1),P("runDestructor",!1),P("releaseClassHandle",!1),P("finalizationRegistry",!1),P("detachFinalizer_deps",!1),P("detachFinalizer",!1),P("attachFinalizer",!1),P("makeClassHandle",!1),P("init_ClassHandle",!1),P("ClassHandle",!1),P("ClassHandle_isAliasOf",!1),P("throwInstanceAlreadyDeleted",!1),P("ClassHandle_clone",!1),P("ClassHandle_delete",!1),P("deletionQueue",!1),P("ClassHandle_isDeleted",!1),P("ClassHandle_deleteLater",!1),P("flushPendingDeletes",!1),P("delayFunction",!1),P("setDelayFunction",!1),P("RegisteredClass",!1),P("shallowCopyInternalPointer",!1),P("downcastPointer",!1),P("upcastPointer",!1),P("validateThis",!1),P("char_0",!1),P("char_9",!1),P("makeLegalFunctionName",!1),P("emval_handle_array",!1),P("emval_free_list",!1),P("emval_symbols",!1),P("init_emval",!1),P("count_emval_handles",!1),P("get_first_emval",!1),P("getStringOrSymbol",!1),P("Emval",!1),P("emval_newers",!1),P("craftEmvalAllocator",!1),P("emval_get_global",!1),P("emval_methodCallers",!1),P("emval_registeredMethods",!1),P("warnOnce",!1),P("stackSave",!1),P("stackRestore",!1),P("stackAlloc",!1),P("AsciiToString",!1),P("stringToAscii",!1),P("UTF16ToString",!1),P("stringToUTF16",!1),P("lengthBytesUTF16",!1),P("UTF32ToString",!1),P("stringToUTF32",!1),P("lengthBytesUTF32",!1),P("allocateUTF8",!1),P("allocateUTF8OnStack",!1),r.writeStackCookie=ne,r.checkStackCookie=oe,C("ALLOC_NORMAL",!1),C("ALLOC_STACK",!1),pe=function e(){Ct||Ft(),Ct||(pe=e)},r.run=Ft,r.preInit)for("function"==typeof r.preInit&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();Ft(),e.exports=r});const u=1e3,c=1e3,d=!1,f=!1,p=!1,m=!1,h="initVideo",g="render",v="playAudio",y="initAudio",E="audioCode",w="videoCode",b=1,_=2,T="init",k="decode",S="audioDecode",C="videoDecode",P="close",A="updateConfig",F="key",D="delta";s(function(e){!function(){var r="undefined"!=typeof window&&void 0!==window.document?window.document:{},t=e.exports,n=function(){for(var e,t=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],n=0,o=t.length,i={};n{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){}})(),Date.now||(Date.now=function(){return(new Date).getTime()}),l.postRun=function(){var e=[],r=[],t={};"VideoEncoder"in self&&(t={hasInit:!1,isEmitInfo:!1,offscreenCanvas:null,offscreenCanvasCtx:null,decoder:new VideoDecoder({output:function(e){if(n.isDestroyed)return;t.isEmitInfo||(n.opt.debug&&console.log("Jb: [worker] Webcodecs Video Decoder initSize"),postMessage({cmd:h,w:e.codedWidth,h:e.codedHeight}),t.isEmitInfo=!0,t.offscreenCanvas=new OffscreenCanvas(e.codedWidth,e.codedHeight),t.offscreenCanvasCtx=t.offscreenCanvas.getContext("2d")),t.offscreenCanvasCtx.drawImage(e,0,0,e.codedWidth,e.codedHeight);let r=t.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:r,delay:n.delay,ts:0},[r]),setTimeout(function(){e.close?e.close():e.destroy()},100)},error:function(e){console.error(e)}}),decode:function(e,r){const o=e[0]>>4==1;if(t.hasInit){const n=new EncodedVideoChunk({data:e.slice(5),timestamp:r,type:o?F:D});t.decoder.decode(n)}else if(o&&0===e[1]){const r=15&e[0];n.setVideoCodec(r);const o=function(e){let r=e.subarray(1,4),t="avc1.";for(let e=0;e<3;e++){let n=r[e].toString(16);n.length<2&&(n="0"+n),t+=n}return{codec:t,description:e}}(e.slice(5));t.decoder.configure(o),t.hasInit=!0}},reset(){t.hasInit=!1,t.isEmitInfo=!1,t.offscreenCanvas=null,t.offscreenCanvasCtx=null}});var n={isDestroyed:!1,opt:{debug:d,useOffscreen:p,useWCS:f,videoBuffer:u,openWebglAlignment:m,videoBufferDelay:c},useOffscreen:function(){return n.opt.useOffscreen&&"undefined"!=typeof OffscreenCanvas},initAudioPlanar:function(e,t){postMessage({cmd:y,sampleRate:t,channels:e});var n=[],o=0;this.playAudioPlanar=function(t,i,a){for(var s=i,u=[],c=0,d=0;d<2;d++){var f=l.HEAPU32[(t>>2)+d]>>2;u[d]=l.HEAPF32.subarray(f,f+s)}if(o){if(!(s>=(i=1024-o)))return o+=s,r[0]=Float32Array.of(...r[0],...u[0]),void(2==e&&(r[1]=Float32Array.of(...r[1],...u[1])));n[0]=Float32Array.of(...r[0],...u[0].subarray(0,i)),2==e&&(n[1]=Float32Array.of(...r[1],...u[1].subarray(0,i))),postMessage({cmd:v,buffer:n,ts:a},n.map(e=>e.buffer)),c=i,s-=i}for(o=s;o>=1024;o-=1024)n[0]=u[0].slice(c,c+=1024),2==e&&(n[1]=u[1].slice(c-1024,c)),postMessage({cmd:v,buffer:n,ts:a},n.map(e=>e.buffer));o&&(r[0]=u[0].slice(c),2==e&&(r[1]=u[1].slice(c)))}},setVideoCodec:function(e){postMessage({cmd:w,code:e})},setAudioCodec:function(e){postMessage({cmd:E,code:e})},setVideoSize:function(e,r){postMessage({cmd:h,w:e,h:r});var t=e*r,o=t>>2;n.useOffscreen()?(this.offscreenCanvas=new OffscreenCanvas(e,r),this.offscreenCanvasGL=this.offscreenCanvas.getContext("webgl"),this.webglObj=((e,r)=>{var t=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),n=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");r&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var o=e.createShader(e.VERTEX_SHADER);e.shaderSource(o,t),e.compileShader(o),e.getShaderParameter(o,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(o));var i=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(i,n),e.compileShader(i),e.getShaderParameter(i,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(i));var a=e.createProgram();e.attachShader(a,o),e.attachShader(a,i),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var s=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,s),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var l=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(l),e.vertexAttribPointer(l,2,e.FLOAT,!1,0,0);var u=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,u),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function d(r,t){var n=e.createTexture();return e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,r),t),n}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var f=d("ySampler",0),p=d("uSampler",1),m=d("vSampler",2);return{render:function(r,t,n,o,i){e.viewport(0,0,r,t),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,f),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r,t,0,e.LUMINANCE,e.UNSIGNED_BYTE,n),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,m),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,i),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(s),e.deleteBuffer(u),e.deleteTexture(f),e.deleteTexture(p),e.deleteTexture(m)}catch(e){}}}})(this.offscreenCanvasGL,n.opt.openWebglAlignment),this.draw=function(n,i,a,s){const u=l.HEAPU8.subarray(i,i+t),c=l.HEAPU8.subarray(a,a+o),d=l.HEAPU8.subarray(s,s+o);this.webglObj.render(e,r,u,c,d);let f=this.offscreenCanvas.transferToImageBitmap();postMessage({cmd:g,buffer:f,delay:this.delay,ts:n},[f])}):this.draw=function(e,r,n,i){const a=[Uint8Array.from(l.HEAPU8.subarray(r,r+t)),Uint8Array.from(l.HEAPU8.subarray(n,n+o)),Uint8Array.from(l.HEAPU8.subarray(i,i+o))];postMessage({cmd:g,output:a,delay:this.delay,ts:e},a.map(e=>e.buffer))}},getDelay:function(e){if(!e)return-1;if(this.firstTimestamp){if(e){const r=Date.now()-this.startTimestamp,t=e-this.firstTimestamp;this.delay=r>=t?r-t:t-r}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay},resetDelay:function(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1},init:function(){n.opt.debug&&console.log("Jb: [worker] init");const r=e=>{n.opt.useWCS&&n.useOffscreen()&&e.type===_&&t.decode?t.decode(e.payload,e.ts):e.decoder.decode(e.payload,e.ts)};this.stopId=setInterval(()=>{if(!n.isDestroyed&&e.length)if(this.dropping){for((t=e.shift()).type===b&&0===t.payload[1]&&r(t);!t.isIFrame&&e.length;)(t=e.shift()).type===b&&0===t.payload[1]&&r(t);t.isIFrame&&(this.dropping=!1,r(t))}else{var t=e[0];if(-1===this.getDelay(t.ts))e.shift(),r(t);else if(this.delay>n.opt.videoBuffer+n.opt.videoBufferDelay)this.resetDelay(),this.dropping=!0;else for(;e.length&&(t=e[0],this.getDelay(t.ts)>n.opt.videoBuffer);)e.shift(),r(t)}},10)},close:function(){n.isDestroyed=!0,n.opt.debug&&console.log("Jb: [worker]: close"),clearInterval(this.stopId),this.stopId=null,o.clear&&o.clear(),i.clear&&i.clear(),t.reset&&t.reset(),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1,this.webglObj&&(this.webglObj.destroy(),this.offscreenCanvas=null,this.offscreenCanvasGL=null,this.offscreenCanvasCtx=null),e=[],r=[],delete this.playAudioPlanar,delete this.draw},pushBuffer:function(r,t){t.type===b?e.push({ts:t.ts,payload:r,decoder:o,type:b}):t.type===_&&e.push({ts:t.ts,payload:r,decoder:i,type:_,isIFrame:t.isIFrame})}},o=new l.AudioDecoder(n),i=new l.VideoDecoder(n);postMessage({cmd:T}),self.onmessage=function(e){var r=e.data;switch(r.cmd){case T:try{n.opt=Object.assign(n.opt,JSON.parse(r.opt))}catch(e){}o.sample_rate=r.sampleRate,n.init();break;case k:n.pushBuffer(r.buffer,r.options);break;case S:o.decode(r.buffer,r.ts);break;case C:i.decode(r.buffer,r.ts);break;case P:n.close();break;case A:n.opt[r.key]=r.value}}}}); diff --git a/web/public/static/js/jessibuca/jessibuca.d.ts b/web/public/static/js/jessibuca/jessibuca.d.ts index b68237354..09a1ac581 100644 --- a/web/public/static/js/jessibuca/jessibuca.d.ts +++ b/web/public/static/js/jessibuca/jessibuca.d.ts @@ -171,7 +171,32 @@ declare namespace Jessibuca { * https://github.com/langhuihui/jessibuca/issues/152 解决方案 * 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。 */ - openWebglAlignment?: boolean + openWebglAlignment?: boolean, + + /** + * webcodecs硬解码是否通过video标签渲染 + */ + wcsUseVideoRender?: boolean, + + /** + * 底部控制台是否自动隐藏 + */ + controlAutoHide?: boolean, + + /** + * 录制的视频格式 + */ + recordType?: 'webm' | 'mp4', + + /** + * 是否使用web全屏(旋转90度)(只会在移动端生效)。 + */ + useWebFullScreen?: boolean, + + /** + * 是否自动使用系统全屏 + */ + autoUseSystemFullScreen?: boolean, } } @@ -222,8 +247,8 @@ declare class Jessibuca { jessibuca.setTimeout(10) jessibuca.on('timeout',function(){ - // - }); + // + }); */ setTimeout(): void; @@ -249,17 +274,17 @@ declare class Jessibuca { * 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。 @example jessibuca.pause().then(()=>{ - console.log('pause success') + console.log('pause success') - jessibuca.play().then(()=>{ + jessibuca.play().then(()=>{ - }).catch((e)=>{ + }).catch((e)=>{ - }) + }) - }).catch((e)=>{ - console.log('pause error',e); - }) + }).catch((e)=>{ + console.log('pause error',e); + }) */ pause(): Promise; @@ -289,14 +314,20 @@ declare class Jessibuca { @example jessibuca.play('url').then(()=>{ - console.log('play success') - }).catch((e)=>{ - console.log('play error',e) - }) - // - jessibuca.play() + console.log('play success') + }).catch((e)=>{ + console.log('play error',e) + }) + // 添加请求头 + jessibuca.play('url',{headers:{'Authorization':'test111'}}).then(()=>{ + console.log('play success') + }).catch((e)=>{ + console.log('play error',e) + }) */ - play(url?: string): Promise; + play(url?: string, options?: { + headers: Object + }): Promise; /** * 重新调整视图大小 @@ -427,6 +458,21 @@ declare class Jessibuca { */ isRecording(): boolean; + /** + * 切换底部控制条 隐藏/显示 + * @param isShow + * + * @example + * jessibuca.toggleControlBar(true) // 显示 + * jessibuca.toggleControlBar(false) // 隐藏 + * jessibuca.toggleControlBar() // 切换 隐藏/显示 + */ + toggleControlBar(isShow:boolean): void; + + /** + * 获取底部控制条是否显示 + */ + getControlBarShow(): boolean; /** * 监听 jessibuca 初始化事件 @@ -477,14 +523,14 @@ declare class Jessibuca { * 错误信息 * @example * jessibuca.on("error",function(error){ - if(error === Jessibuca.ERROR.fetchError){ - // - } - else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){ - // - } - console.log('error:',error) - }) + if(error === Jessibuca.ERROR.fetchError){ + // + } + else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){ + // + } + console.log('error:',error) + }) */ on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void; diff --git a/web/public/static/js/jessibuca/jessibuca.js b/web/public/static/js/jessibuca/jessibuca.js index ba730b91d..e9ac986f4 100644 --- a/web/public/static/js/jessibuca/jessibuca.js +++ b/web/public/static/js/jessibuca/jessibuca.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).jessibuca=t()}(this,(function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e,t){return e(t={exports:{}},t.exports),t.exports}var i,o=t((function(e){e.exports=function(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e},e.exports.__esModule=!0,e.exports.default=e.exports})),r=(i=o)&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i;const s=0,a=1,n="flv",A="m7s",d="mp4",c="webm",l={videoBuffer:1e3,videoBufferDelay:1e3,isResize:!0,isFullResize:!1,isFlv:!1,debug:!1,hotKey:!1,loadingTimeout:10,heartTimeout:5,timeout:10,loadingTimeoutReplay:!0,heartTimeoutReplay:!0,loadingTimeoutReplayTimes:3,heartTimeoutReplayTimes:3,supportDblclickFullscreen:!1,showBandwidth:!1,keepScreenOn:!1,isNotMute:!1,hasAudio:!0,hasVideo:!0,operateBtns:{fullscreen:!1,screenshot:!1,play:!1,audio:!1,record:!1},controlAutoHide:!1,hasControl:!1,loadingText:"",background:"",decoder:"decoder.js",url:"",rotate:0,forceNoOffscreen:!0,hiddenAutoPause:!1,protocol:a,demuxType:n,useWCS:!1,wcsUseVideoRender:!0,useMSE:!1,useOffscreen:!1,autoWasm:!0,wasmDecodeErrorReplay:!0,openWebglAlignment:!1,wasmDecodeAudioSyncVideo:!1,recordType:c,useWebFullScreen:!1},u="init",h="initVideo",p="render",m="playAudio",g="initAudio",f="audioCode",b="videoCode",y="wasmError",v="Invalid NAL unit size",w=1,S=2,E=8,B=9,C="init",R="decode",k="audioDecode",T="close",I="updateConfig",x={fullscreen:"fullscreen$2",webFullscreen:"webFullscreen",decoderWorkerInit:"decoderWorkerInit",play:"play",playing:"playing",pause:"pause",mute:"mute",load:"load",loading:"loading",videoInfo:"videoInfo",timeUpdate:"timeUpdate",audioInfo:"audioInfo",log:"log",error:"error",kBps:"kBps",timeout:"timeout",delayTimeout:"delayTimeout",loadingTimeout:"loadingTimeout",stats:"stats",performance:"performance",record:"record",recording:"recording",recordingTimestamp:"recordingTimestamp",recordStart:"recordStart",recordEnd:"recordEnd",recordCreateError:"recordCreateError",buffer:"buffer",videoFrame:"videoFrame",start:"start",metadata:"metadata",resize:"resize",streamEnd:"streamEnd",streamSuccess:"streamSuccess",streamMessage:"streamMessage",streamError:"streamError",volumechange:"volumechange",destroy:"destroy",mseSourceOpen:"mseSourceOpen",mseSourceClose:"mseSourceClose",mseSourceBufferError:"mseSourceBufferError",mseSourceBufferBusy:"mseSourceBufferBusy",mseSourceBufferFull:"mseSourceBufferFull",videoWaiting:"videoWaiting",videoTimeUpdate:"videoTimeUpdate",videoSyncAudio:"videoSyncAudio",playToRenderTimes:"playToRenderTimes"},D={load:x.load,timeUpdate:x.timeUpdate,videoInfo:x.videoInfo,audioInfo:x.audioInfo,error:x.error,kBps:x.kBps,log:x.log,start:x.start,timeout:x.timeout,loadingTimeout:x.loadingTimeout,delayTimeout:x.delayTimeout,fullscreen:"fullscreen",webFullscreen:x.webFullscreen,play:x.play,pause:x.pause,mute:x.mute,stats:x.stats,volumechange:x.volumechange,performance:x.performance,recordingTimestamp:x.recordingTimestamp,recordStart:x.recordStart,recordEnd:x.recordEnd,playToRenderTimes:x.playToRenderTimes},j={playError:"playIsNotPauseOrUrlIsNull",fetchError:"fetchError",websocketError:"websocketError",webcodecsH265NotSupport:"webcodecsH265NotSupport",webcodecsDecodeError:"webcodecsDecodeError",webcodecsWidthOrHeightChange:"webcodecsWidthOrHeightChange",mediaSourceH265NotSupport:"mediaSourceH265NotSupport",mediaSourceFull:x.mseSourceBufferFull,mseSourceBufferError:x.mseSourceBufferError,mediaSourceAppendBufferError:"mediaSourceAppendBufferError",mediaSourceBufferListLarge:"mediaSourceBufferListLarge",mediaSourceAppendBufferEndTimeout:"mediaSourceAppendBufferEndTimeout",wasmDecodeError:"wasmDecodeError",webglAlignmentError:"webglAlignmentError"},L="notConnect",F="open",O="close",V="error",M={download:"download",base64:"base64",blob:"blob"},U={7:"H264(AVC)",12:"H265(HEVC)"},Q=12,W={10:"AAC",7:"ALAW",8:"MULAW"},J=38,P=0,G=1,N=2,H="webcodecs",z="webgl",Y="offscreen",X="key",q="delta",Z='video/mp4; codecs="avc1.64002A"',K="ended",_="open",$="closed",ee=1e3,te=27,ie=38,oe=40,re="A key frame is required after configure() or flush()",se="Cannot call 'decode' on a closed codec",ae="The user aborted a request",ne="AbortError",Ae="AbortError",de=0,ce=1,le=3,ue=16;class he{constructor(e){this.log=function(t){if(e._opt.debug){for(var i=arguments.length,o=new Array(i>1?i-1:0),r=1;r1?i-1:0),r=1;r1?t-1:0),o=1;o3&&void 0!==arguments[3]?arguments[3]:{};if(!e)return;if(Array.isArray(t))return t.map((t=>this.proxy(e,t,i,o)));e.addEventListener(t,i,o);const r=()=>e.removeEventListener(t,i,o);return this.destroys.push(r),r}destroy(){this.master.debug&&this.master.debug.log("Events","destroy"),this.destroys.forEach((e=>e()))}}var me=t((function(e){!function(){var t="undefined"!=typeof window&&void 0!==window.document?window.document:{},i=e.exports,o=function(){for(var e,i=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],o=0,r=i.length,s={};o0&&void 0!==arguments[0]?arguments[0]:"";const t=e.split(","),i=atob(t[1]),o=t[0].replace("data:","").replace(";base64","");let r=i.length,s=new Uint8Array(r);for(;r--;)s[r]=i.charCodeAt(r);return new File([s],"file",{type:o})}function be(){return(new Date).getTime()}function ye(e,t,i){return Math.max(Math.min(e,Math.max(t,i)),Math.min(t,i))}function ve(e,t,i){if(e)return"object"==typeof t&&Object.keys(t).forEach((i=>{ve(e,i,t[i])})),e.style[t]=i,e}function we(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(!e)return 0;const o=getComputedStyle(e,null).getPropertyValue(t);return i?parseFloat(o):o}function Se(){return performance&&"function"==typeof performance.now?performance.now():Date.now()}function Ee(e){let t=0,i=Se();return o=>{t+=o;const r=Se(),s=r-i;s>=1e3&&(e(t/s*1e3),i=r,t=0)}}function Be(){return/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase())}function Ce(e){if(null==e||""===e||0===parseInt(e)||isNaN(parseInt(e)))return"0KB/s";let t=parseFloat(e);return t=t.toFixed(2),t+"KB/s"}function Re(e){return null==e}function ke(e){return!Re(e)}function Te(e){const t=e||window.event;return t.target||t.srcElement}function Ie(e){let t=!1;return e&&e.parentNode&&(e.parentNode.removeChild(e),t=!0),t}function xe(e,t){let i=[];i[0]=t?28:44,i[1]=1,i[2]=0,i[3]=0,i[4]=0;const o=new Uint8Array(i.length+e.byteLength);return o.set(i,0),o.set(e,i.length),o}me.isEnabled,(()=>{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){}})();class De{on(e,t,i){const o=this.e||(this.e={});return(o[e]||(o[e]=[])).push({fn:t,ctx:i}),this}once(e,t,i){const o=this;function r(){o.off(e,r);for(var s=arguments.length,a=new Array(s),n=0;n1?i-1:0),r=1;r{delete i[e]})),void delete this.e;const o=i[e],r=[];if(o&&t)for(let e=0,i=o.length;e=200&&t.status<=299}function Ve(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 Me=Le.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),Ue="object"!=typeof window||window!==Le?function(){}:"download"in HTMLAnchorElement.prototype&&!Me?function(e,t,i){var o=Le.URL||Le.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?Oe(r.href)?Fe(e,t,i):Ve(r,r.target="_blank"):Ve(r)):(r.href=o.createObjectURL(e),setTimeout((function(){o.revokeObjectURL(r.href)}),4e4),setTimeout((function(){Ve(r)}),0))}:"msSaveOrOpenBlob"in navigator?function(e,t,i){if(t=t||e.name||"download","string"==typeof e)if(Oe(e))Fe(e,t,i);else{var o=document.createElement("a");o.href=e,o.target="_blank",setTimeout((function(){Ve(o)}))}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,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof e)return Fe(e,t,i);var r="application/octet-stream"===e.type,s=/constructor/i.test(Le.HTMLElement)||Le.safari,a=/CriOS\/[\d]+/.test(navigator.userAgent);if((a||r&&s||Me)&&"undefined"!=typeof FileReader){var n=new FileReader;n.onloadend=function(){var e=n.result;e=a?e:e.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=e:location=e,o=null},n.readAsDataURL(e)}else{var A=Le.URL||Le.webkitURL,d=A.createObjectURL(e);o?o.location=d:location.href=d,o=null,setTimeout((function(){A.revokeObjectURL(d)}),4e4)}};class Qe extends je{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.contextGlRender=null,this.contextGlDestroy=null,this.bitmaprenderer=null,this.renderType=null,this.videoInfo={width:"",height:"",encType:""},this._initCanvasRender(),this.player.debug.log("CanvasVideo","init")}destroy(){super.destroy(),this.contextGl&&(this.contextGl=null),this.context2D&&(this.context2D=null),this.contextGlRender&&(this.contextGlDestroy&&this.contextGlDestroy(),this.contextGlDestroy=null,this.contextGlRender=null),this.bitmaprenderer&&(this.bitmaprenderer=null),this.renderType=null,this.player.debug.log("CanvasVideoLoader","destroy")}_initContextGl(){if(this.contextGl=function(e){let t=null;const i=["webgl","experimental-webgl","moz-webgl","webkit-3d"];let o=0;for(;!t&&o{var i=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),o=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");t&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var r=e.createShader(e.VERTEX_SHADER);e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(r));var s=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(s,o),e.compileShader(s),e.getShaderParameter(s,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(s));var a=e.createProgram();e.attachShader(a,r),e.attachShader(a,s),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var A=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(A),e.vertexAttribPointer(A,2,e.FLOAT,!1,0,0);var d=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,d),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function l(t,i){var o=e.createTexture();return e.bindTexture(e.TEXTURE_2D,o),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,t),i),o}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var u=l("ySampler",0),h=l("uSampler",1),p=l("vSampler",2);return{render:function(t,i,o,r,s){e.viewport(0,0,t,i),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,u),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t,i,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,h),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,r),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,s),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(n),e.deleteBuffer(d),e.deleteTexture(u),e.deleteTexture(h),e.deleteTexture(p)}catch(e){}}}})(this.contextGl,this.player._opt.openWebglAlignment);this.contextGlRender=e.render,this.contextGlDestroy=e.destroy}else this.player.debug.error("CanvasVideoLoader","init webgl fail")}_initContext2D(){this.context2D=this.$videoElement.getContext("2d")}_initCanvasRender(){this.player._opt.useWCS&&!this._supportOffscreen()?(this.renderType=H,this._initContext2D()):this._supportOffscreen()?(this.renderType=Y,this._bindOffscreen()):(this.renderType=z,this._initContextGl())}_supportOffscreen(){return"function"==typeof this.$videoElement.transferControlToOffscreen&&this.player._opt.useOffscreen}_bindOffscreen(){this.bitmaprenderer=this.$videoElement.getContext("bitmaprenderer")}initCanvasViewSize(){this.$videoElement.width=this.videoInfo.width,this.$videoElement.height=this.videoInfo.height,this.resize()}render(e){switch(this.player.videoTimestamp=e.ts,this.renderType){case Y:this.bitmaprenderer.transferFromImageBitmap(e.buffer);break;case z:this.contextGlRender(this.$videoElement.width,this.$videoElement.height,e.output[0],e.output[1],e.output[2]);break;case H:this.context2D.drawImage(e.videoFrame,0,0,this.$videoElement.width,this.$videoElement.height),(t=e.videoFrame).close?t.close():t.destroy&&t.destroy()}var t}screenshot(e,t,i,o){e=e||be(),o=o||M.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&M[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement.toDataURL(r[t]||r.png,s);if(o===M.base64)return a;{const t=fe(a);if(o===M.blob)return t;o===M.download&&Ue(t,e)}}clearView(){switch(this.renderType){case Y:(function(e,t){const i=document.createElement("canvas");return i.width=e,i.height=t,window.createImageBitmap(i,0,0,e,t)})(this.$videoElement.width,this.$videoElement.height).then((e=>{this.bitmaprenderer.transferFromImageBitmap(e)}));break;case z:this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT);break;case H:this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height)}}resize(){this.player.debug.log("canvasVideo","resize");const e=this.player._opt;let t=this.player.width,i=this.player.height;e.hasControl&&!e.controlAutoHide&&(Be()&&this.player.fullscreen&&e.useWebFullScreen?t-=J:i-=J);let o=this.$videoElement.width,r=this.$videoElement.height;const s=e.rotate;let a=(t-o)/2,n=(i-r)/2;270!==s&&90!==s||(o=this.$videoElement.height,r=this.$videoElement.width);const A=t/o,d=i/r;let c=A>d?d:A;e.isResize||A!==d&&(c=A+","+d),e.isFullResize&&(c=A>d?A:d);let l="scale("+c+")";s&&(l+=" rotate("+s+"deg)"),this.$videoElement.style.transform=l,this.$videoElement.style.left=a+"px",this.$videoElement.style.top=n+"px"}}class We extends je{constructor(e){super(),this.player=e;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.videoInfo={width:"",height:"",encType:""};const o=this.player._opt;o.useWCS&&o.wcsUseVideoRender&&(this.trackGenerator=new MediaStreamTrackGenerator({kind:"video"}),t.srcObject=new MediaStream([this.trackGenerator]),this.vwriter=this.trackGenerator.writable.getWriter()),this.$videoElement=t,this.$canvasElement=i,this.canvasContext=i.getContext("2d"),this.fixChromeVideoFlashBug(),this.resize();const{proxy:r}=this.player.events;r(this.$videoElement,"canplay",(()=>{this.player.debug.log("Video",`canplay and _delayPlay is ${this._delayPlay}`),this._delayPlay&&this._play()})),r(this.$videoElement,"waiting",(()=>{this.player.emit(x.videoWaiting)})),r(this.$videoElement,"timeupdate",(e=>{const t=parseInt(e.timeStamp,10);this.player.emit(x.timeUpdate,t),!this.isPlaying()&&this.init&&(this.player.debug.log("Video","timeupdate and this.isPlaying is false and retry play"),this.$videoElement.play())})),this.player.debug.log("Video","init")}destroy(){super.destroy(),this.$canvasElement=null,this.canvasContext=null,this.$videoElement&&(this.$videoElement.pause(),this.$videoElement.currentTime=0,this.$videoElement.src="",this.$videoElement.removeAttribute("src"),this.$videoElement=null),this.trackGenerator&&(this.trackGenerator.stop(),this.trackGenerator=null),this.vwriter&&(this.vwriter.close(),this.vwriter=null),this.player.debug.log("Video","destroy")}fixChromeVideoFlashBug(){const e=function(){const e=navigator.userAgent.toLowerCase(),t={},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 o in i)if(i[o]){let i="";if("IE"===o)i=e.match(/(msie\s|trident.*rv:)([\w.]+)/)[2];else if("Chrome"===o){for(let e in navigator.mimeTypes)"application/360softmgrplugin"===navigator.mimeTypes[e].type&&(o="360");i=e.match(/chrome\/([\d.]+)/)[1]}else"Firefox"===o?i=e.match(/firefox\/([\d.]+)/)[1]:"Opera"===o?i=e.match(/opera\/([\d.]+)/)[1]:"Safari"===o?i=e.match(/version\/([\d.]+)/)[1]:"Edge"===o?i=e.match(/edge\/([\d.]+)/)[1]:"QQBrowser"===o&&(i=e.match(/qqbrowser\/([\d.]+)/)[1]);t.type=o,t.version=parseInt(i)}return t}().type.toLowerCase();if("chrome"===e||"edge"===e){const e=this.player.$container;e.style.backdropFilter="blur(0px)",e.style.translateZ="0"}}play(){if(this.$videoElement){const e=this._getVideoReadyState();if(this.player.debug.log("Video",`play and readyState: ${e}`),0===e)return this.player.debug.warn("Video","readyState is 0 and set _delayPlay to true"),void(this._delayPlay=!0);this._play()}}_getVideoReadyState(){let e=0;return this.$videoElement&&(e=this.$videoElement.readyState),e}_play(){this.$videoElement&&this.$videoElement.play().then((()=>{this._delayPlay=!1,this.player.debug.log("Video","_play success"),setTimeout((()=>{this.isPlaying()||(this.player.debug.warn("Video","play failed and retry play"),this._play())}),100)})).catch((e=>{this.player.debug.error("Video","_play error",e)}))}pause(e){e?this.$videoElement&&this.$videoElement.pause():setTimeout((()=>{this.$videoElement&&this.$videoElement.pause()}),100)}clearView(){}screenshot(e,t,i,o){e=e||be(),o=o||M.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&M[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement;let n=this.$canvasElement;n.width=a.videoWidth,n.height=a.videoHeight,this.canvasContext.drawImage(a,0,0,n.width,n.height);const A=n.toDataURL(r[t]||r.png,s);if(this.canvasContext.clearRect(0,0,n.width,n.height),n.width=0,n.height=0,o===M.base64)return A;{const t=fe(A);if(o===M.blob)return t;o===M.download&&Ue(t,e)}}initCanvasViewSize(){this.resize()}render(e){this.vwriter&&this.vwriter.write(e.videoFrame)}resize(){let e=this.player.width,t=this.player.height;const i=this.player._opt,o=i.rotate;i.hasControl&&!i.controlAutoHide&&(Be()&&this.player.fullscreen&&i.useWebFullScreen?e-=J:t-=J),this.$videoElement.width=e,this.$videoElement.height=t,270!==o&&90!==o||(this.$videoElement.width=t,this.$videoElement.height=e);let r=(e-this.$videoElement.width)/2,s=(t-this.$videoElement.height)/2,a="contain";i.isResize||(a="fill"),i.isFullResize&&(a="none"),this.$videoElement.style.objectFit=a,this.$videoElement.style.transform="rotate("+o+"deg)",this.$videoElement.style.left=r+"px",this.$videoElement.style.top=s+"px"}isPlaying(){return this.$videoElement&&!this.$videoElement.paused}}class Je{constructor(e){return new(Je.getLoaderFactory(e._opt))(e)}static getLoaderFactory(e){return e.useMSE||e.useWCS&&!e.useOffscreen&&e.wcsUseVideoRender?We:Qe}}class Pe extends De{constructor(e){super(),this.bufferList=[],this.player=e,this.scriptNode=null,this.hasInitScriptNode=!1,this.audioContextChannel=null,this.audioContext=new(window.AudioContext||window.webkitAudioContext),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.audioEnabled(!0),this.gainNode.gain.value=0,this.playing=!1,this.audioSyncVideoOption={diff:null},this.audioInfo={encType:"",channels:"",sampleRate:""},this.init=!1,this.hasAudio=!1,this.on(x.videoSyncAudio,(e=>{this.audioSyncVideoOption=e})),this.player.debug.log("AudioContext","init")}resetInit(){this.init=!1,this.audioInfo={encType:"",channels:"",sampleRate:""}}destroy(){this.closeAudio(),this.resetInit(),this.audioContext.close(),this.audioContext=null,this.gainNode=null,this.hasAudio=!1,this.playing=!1,this.scriptNode&&(this.scriptNode.onaudioprocess=ge,this.scriptNode=null),this.audioBufferSourceNode=null,this.mediaStreamAudioDestinationNode=null,this.hasInitScriptNode=!1,this.audioSyncVideoOption={diff:null},this.off(),this.player.debug.log("AudioContext","destroy")}updateAudioInfo(e){e.encTypeCode&&(this.audioInfo.encType=W[e.encTypeCode],this.audioInfo.encTypeCode=e.encTypeCode),e.channels&&(this.audioInfo.channels=e.channels),e.sampleRate&&(this.audioInfo.sampleRate=e.sampleRate),this.audioInfo.sampleRate&&this.audioInfo.channels&&this.audioInfo.encType&&!this.init&&(this.player.emit(x.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}initScriptNode(){if(this.playing=!0,this.hasInitScriptNode)return;const e=this.audioInfo.channels,t=this.audioContext.createScriptProcessor(1024,0,e);t.onaudioprocess=t=>{const i=t.outputBuffer;if(this.bufferList.length&&this.playing){if(!this.player._opt.useWCS&&!this.player._opt.useMSE&&this.player._opt.wasmDecodeAudioSyncVideo){if(this.audioSyncVideoOption.diff>ee)return void this.player.debug.warn("AudioContext",`audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`);if(this.audioSyncVideoOption.diff<-1e3){this.player.debug.warn("AudioContext",`audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`);let e=this.bufferList.shift();for(;e.ts-this.player.videoTimestamp<-1e3&&this.bufferList.length>0;)e=this.bufferList.shift();if(0===this.bufferList.length)return}}if(0===this.bufferList.length)return;const t=this.bufferList.shift();t&&t.ts&&(this.player.audioTimestamp=t.ts);for(let o=0;o20&&(this.player.debug.warn("AudioContext",`bufferList is large: ${this.bufferList.length}`),this.bufferList.length>50&&this.bufferList.shift()))}pause(){this.audioSyncVideoOption={diff:null},this.playing=!1,this.clear()}resume(){this.playing=!0}}class Ge{constructor(e){return new(Ge.getLoaderFactory())(e)}static getLoaderFactory(){return Pe}}class Ne extends De{constructor(e){super(),this.player=e,this.playing=!1,this.abortController=new AbortController,this.streamRate=Ee((t=>{e.emit(x.kBps,(t/1024).toFixed(2))})),e.debug.log("FetchStream","init")}destroy(){this.abort(),this.off(),this.streamRate=null,this.player.debug.log("FetchStream","destroy")}fetchStream(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{demux:i}=this.player;this.player.debug.log("FetchStream","fetchStream",e,JSON.stringify(t)),this.player._times.streamStart=be();const o=Object.assign({signal:this.abortController.signal},{headers:t.headers||{}});fetch(e,o).then((e=>{const t=e.body.getReader();this.emit(x.streamSuccess);const o=()=>{t.read().then((e=>{let{done:t,value:r}=e;t?i.close():(this.streamRate&&this.streamRate(r.byteLength),i.dispatch(r),o())})).catch((e=>{i.close();const t=e.toString();-1===t.indexOf(ae)&&-1===t.indexOf(ne)&&e.name!==Ae&&(this.abort(),this.emit(j.fetchError,e),this.player.emit(x.error,j.fetchError))}))};o()})).catch((e=>{"AbortError"!==e.name&&(i.close(),this.abort(),this.emit(j.fetchError,e),this.player.emit(x.error,j.fetchError))}))}abort(){this.abortController&&(this.abortController.abort(),this.abortController=null)}}class He extends De{constructor(e){super(),this.player=e,this.socket=null,this.socketStatus=L,this.wsUrl=null,this.streamRate=Ee((t=>{e.emit(x.kBps,(t/1024).toFixed(2))})),e.debug.log("WebsocketLoader","init")}destroy(){this.socket&&(this.socket.close(1e3,"Client disconnecting"),this.socket=null),this.socketStatus=L,this.streamRate=null,this.wsUrl=null,this.off(),this.player.debug.log("websocketLoader","destroy")}_createWebSocket(){const e=this.player,{debug:t,events:{proxy:i},demux:o}=e;this.socket=new WebSocket(this.wsUrl),this.socket.binaryType="arraybuffer",i(this.socket,"open",(()=>{this.emit(x.streamSuccess),t.log("websocketLoader","socket open"),this.socketStatus=F})),i(this.socket,"message",(e=>{this.streamRate&&this.streamRate(e.data.byteLength),this._handleMessage(e.data)})),i(this.socket,"close",(()=>{t.log("websocketLoader","socket close"),this.emit(x.streamEnd),this.socketStatus=O})),i(this.socket,"error",(e=>{t.log("websocketLoader","socket error"),this.emit(j.websocketError,e),this.player.emit(x.error,j.websocketError),this.socketStatus=V,o.close(),t.log("websocketLoader","socket error:",e)}))}_handleMessage(e){const{demux:t}=this.player;t?t.dispatch(e):this.player.debug.warn("websocketLoader","websocket handle message demux is null")}fetchStream(e,t){this.player._times.streamStart=be(),this.wsUrl=e,this._createWebSocket()}}class ze{constructor(e){return new(ze.getLoaderFactory(e._opt.protocol))(e)}static getLoaderFactory(e){return e===a?Ne:e===s?He:void 0}}var Ye=t((function(t){function i(e,t){if(!e)throw"First parameter is required.";t=new o(e,t=t||{type:"video"});var s=this;function a(i){i&&(t.initCallback=function(){i(),i=t.initCallback=null});var o=new r(e,t);(h=new o(e,t)).record(),u("recording"),t.disableLogs||console.log("Initialized recorderType:",h.constructor.name,"for output-type:",t.type)}function n(e){if(e=e||function(){},h){if("paused"===s.state)return s.resumeRecording(),void setTimeout((function(){n(e)}),1);"recording"===s.state||t.disableLogs||console.warn('Recording state should be: "recording", however current state is: ',s.state),t.disableLogs||console.log("Stopped recording "+t.type+" stream."),"gif"!==t.type?h.stop(i):(h.stop(),i()),u("stopped")}else m();function i(i){if(h){Object.keys(h).forEach((function(e){"function"!=typeof h[e]&&(s[e]=h[e])}));var o=h.blob;if(!o){if(!i)throw"Recording failed.";h.blob=o=i}if(o&&!t.disableLogs&&console.log(o.type,"->",b(o.size)),e){var r;try{r=l.createObjectURL(o)}catch(e){}"function"==typeof e.call?e.call(s,r):e(r)}t.autoWriteToDisk&&d((function(e){var i={};i[t.type+"Blob"]=e,x.Store(i)}))}else"function"==typeof e.call?e.call(s,""):e("")}}function A(e){postMessage((new FileReaderSync).readAsDataURL(e))}function d(e,i){if(!e)throw"Pass a callback function over getDataURL.";var o=i?i.blob:(h||{}).blob;if(!o)return t.disableLogs||console.warn("Blob encoder did not finish its job yet."),void setTimeout((function(){d(e,i)}),1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var r=new FileReader;r.readAsDataURL(o),r.onload=function(t){e(t.target.result)}}else{var s=function(e){try{var t=l.createObjectURL(new Blob([e.toString(),"this.onmessage = function (eee) {"+e.name+"(eee.data);}"],{type:"application/javascript"})),i=new Worker(t);return l.revokeObjectURL(t),i}catch(e){}}(A);s.onmessage=function(t){e(t.data)},s.postMessage(o)}}function c(e){e=e||0,"paused"!==s.state?"stopped"!==s.state&&(e>=s.recordingDuration?n(s.onRecordingStopped):(e+=1e3,setTimeout((function(){c(e)}),1e3))):setTimeout((function(){c(e)}),1e3)}function u(e){s&&(s.state=e,"function"==typeof s.onStateChanged.call?s.onStateChanged.call(s,e):s.onStateChanged(e))}var h,p='It seems that recorder is destroyed or "startRecording" is not invoked for '+t.type+" recorder.";function m(){!0!==t.disableLogs&&console.warn(p)}var g={startRecording:function(i){return t.disableLogs||console.log("RecordRTC version: ",s.version),i&&(t=new o(e,i)),t.disableLogs||console.log("started recording "+t.type+" stream."),h?(h.clearRecordedData(),h.record(),u("recording"),s.recordingDuration&&c(),s):(a((function(){s.recordingDuration&&c()})),s)},stopRecording:n,pauseRecording:function(){h?"recording"===s.state?(u("paused"),h.pause(),t.disableLogs||console.log("Paused recording.")):t.disableLogs||console.warn("Unable to pause the recording. Recording state: ",s.state):m()},resumeRecording:function(){h?"paused"===s.state?(u("recording"),h.resume(),t.disableLogs||console.log("Resumed recording.")):t.disableLogs||console.warn("Unable to resume the recording. Recording state: ",s.state):m()},initRecorder:a,setRecordingDuration:function(e,t){if(void 0===e)throw"recordingDuration is required.";if("number"!=typeof e)throw"recordingDuration must be a number.";return s.recordingDuration=e,s.onRecordingStopped=t||function(){},{onRecordingStopped:function(e){s.onRecordingStopped=e}}},clearRecordedData:function(){h?(h.clearRecordedData(),t.disableLogs||console.log("Cleared old recorded data.")):m()},getBlob:function(){if(h)return h.blob;m()},getDataURL:d,toURL:function(){if(h)return l.createObjectURL(h.blob);m()},getInternalRecorder:function(){return h},save:function(e){h?y(h.blob,e):m()},getFromDisk:function(e){h?i.getFromDisk(t.type,e):m()},setAdvertisementArray:function(e){t.advertisement=[];for(var i=e.length,o=0;o-1&&"netscape"in window&&/ rv:/.test(navigator.userAgent),m=!h&&!u&&!!navigator.webkitGetUserMedia||v()||-1!==navigator.userAgent.toLowerCase().indexOf("chrome/"),g=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);g&&!m&&-1!==navigator.userAgent.indexOf("CriOS")&&(g=!1,m=!0);var f=window.MediaStream;function b(e){if(0===e)return"0 Bytes";var t=parseInt(Math.floor(Math.log(e)/Math.log(1e3)),10);return(e/Math.pow(1e3,t)).toPrecision(3)+" "+["Bytes","KB","MB","GB","TB"][t]}function y(e,t){if(!e)throw"Blob object is required.";if(!e.type)try{e.type="video/webm"}catch(e){}var i=(e.type||"video/webm").split("/")[1];if(-1!==i.indexOf(";")&&(i=i.split(";")[0]),t&&-1!==t.indexOf(".")){var o=t.split(".");t=o[0],i=o[1]}var r=(t||Math.round(9999999999*Math.random())+888888888)+"."+i;if(void 0!==navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(e,r);if(void 0!==navigator.msSaveBlob)return navigator.msSaveBlob(e,r);var s=document.createElement("a");s.href=l.createObjectURL(e),s.download=r,s.style="display:none;opacity:0;color:transparent;",(document.body||document.documentElement).appendChild(s),"function"==typeof s.click?s.click():(s.target="_blank",s.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))),l.revokeObjectURL(s.href)}function v(){return"undefined"!=typeof window&&"object"==typeof window.process&&"renderer"===window.process.type||(!("undefined"==typeof process||"object"!=typeof process.versions||!process.versions.electron)||"object"==typeof navigator&&"string"==typeof navigator.userAgent&&navigator.userAgent.indexOf("Electron")>=0)}function w(e,t){return e&&e.getTracks?e.getTracks().filter((function(e){return e.kind===(t||"audio")})):[]}function S(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}void 0===f&&"undefined"!=typeof webkitMediaStream&&(f=webkitMediaStream),void 0!==f&&void 0===f.prototype.stop&&(f.prototype.stop=function(){this.getTracks().forEach((function(e){e.stop()}))}),i.invokeSaveAsDialog=y,i.getTracks=w,i.getSeekableBlob=function(e,t){if("undefined"==typeof EBML)throw new Error("Please link: https://www.webrtc-experiment.com/EBML.js");var i=new EBML.Reader,o=new EBML.Decoder,r=EBML.tools,s=new FileReader;s.onload=function(e){o.decode(this.result).forEach((function(e){i.read(e)})),i.stop();var s=r.makeMetadataSeekable(i.metadatas,i.duration,i.cues),a=this.result.slice(i.metadataSize),n=new Blob([s,a],{type:"video/webm"});t(n)},s.readAsArrayBuffer(e)},i.bytesToSize=b,i.isElectron=v;var E={};function B(){if(p||g||u)return!0;var e,t,i=navigator.userAgent,o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10);return(m||h)&&(e=i.indexOf("Chrome"),o=i.substring(e+7)),-1!==(t=o.indexOf(";"))&&(o=o.substring(0,t)),-1!==(t=o.indexOf(" "))&&(o=o.substring(0,t)),r=parseInt(""+o,10),isNaN(r)&&(o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),r>=49}function C(e,t){var i=this;if(void 0===e)throw'First argument "MediaStream" is required.';if("undefined"==typeof MediaRecorder)throw"Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";if("audio"===(t=t||{mimeType:"video/webm"}).type){var o;if(w(e,"video").length&&w(e,"audio").length)navigator.mozGetUserMedia?(o=new f).addTrack(w(e,"audio")[0]):o=new f(w(e,"audio")),e=o;t.mimeType&&-1!==t.mimeType.toString().toLowerCase().indexOf("audio")||(t.mimeType=m?"audio/webm":"audio/ogg"),t.mimeType&&"audio/ogg"!==t.mimeType.toString().toLowerCase()&&navigator.mozGetUserMedia&&(t.mimeType="audio/ogg")}var r,s=[];function a(){i.timestamps.push((new Date).getTime()),"function"==typeof t.onTimeStamp&&t.onTimeStamp(i.timestamps[i.timestamps.length-1],i.timestamps)}function n(e){return r&&r.mimeType?r.mimeType:e.mimeType||"video/webm"}function A(){s=[],r=null,i.timestamps=[]}this.getArrayOfBlobs=function(){return s},this.record=function(){i.blob=null,i.clearRecordedData(),i.timestamps=[],d=[],s=[];var o=t;t.disableLogs||console.log("Passing following config over MediaRecorder API.",o),r&&(r=null),m&&!B()&&(o="video/vp8"),"function"==typeof MediaRecorder.isTypeSupported&&o.mimeType&&(MediaRecorder.isTypeSupported(o.mimeType)||(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType),o.mimeType="audio"===t.type?"audio/webm":"video/webm"));try{r=new MediaRecorder(e,o),t.mimeType=o.mimeType}catch(t){r=new MediaRecorder(e)}o.mimeType&&!MediaRecorder.isTypeSupported&&"canRecordMimeType"in r&&!1===r.canRecordMimeType(o.mimeType)&&(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType)),r.ondataavailable=function(e){if(e.data&&d.push("ondataavailable: "+b(e.data.size)),"number"!=typeof t.timeSlice)!e.data||!e.data.size||e.data.size<100||i.blob?i.recordingCallback&&(i.recordingCallback(new Blob([],{type:n(o)})),i.recordingCallback=null):(i.blob=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)}),i.recordingCallback&&(i.recordingCallback(i.blob),i.recordingCallback=null));else if(e.data&&e.data.size&&(s.push(e.data),a(),"function"==typeof t.ondataavailable)){var r=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)});t.ondataavailable(r)}},r.onstart=function(){d.push("started")},r.onpause=function(){d.push("paused")},r.onresume=function(){d.push("resumed")},r.onstop=function(){d.push("stopped")},r.onerror=function(e){e&&(e.name||(e.name="UnknownError"),d.push("error: "+e),t.disableLogs||(-1!==e.name.toString().toLowerCase().indexOf("invalidstate")?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.",e):-1!==e.name.toString().toLowerCase().indexOf("notsupported")?console.error("MIME type (",o.mimeType,") is not supported.",e):-1!==e.name.toString().toLowerCase().indexOf("security")?console.error("MediaRecorder security error",e):"OutOfMemory"===e.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"IllegalStreamModification"===e.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"OtherRecordingError"===e.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"GenericError"===e.name?console.error("The UA cannot provide the codec or recording option that has been requested.",e):console.error("MediaRecorder Error",e)),function(e){if(!i.manuallyStopped&&r&&"inactive"===r.state)return delete t.timeslice,void r.start(6e5);setTimeout(void 0,1e3)}(),"inactive"!==r.state&&"stopped"!==r.state&&r.stop())},"number"==typeof t.timeSlice?(a(),r.start(t.timeSlice)):r.start(36e5),t.initCallback&&t.initCallback()},this.timestamps=[],this.stop=function(e){e=e||function(){},i.manuallyStopped=!0,r&&(this.recordingCallback=e,"recording"===r.state&&r.stop(),"number"==typeof t.timeSlice&&setTimeout((function(){i.blob=new Blob(s,{type:n(t)}),i.recordingCallback(i.blob)}),100))},this.pause=function(){r&&"recording"===r.state&&r.pause()},this.resume=function(){r&&"paused"===r.state&&r.resume()},this.clearRecordedData=function(){r&&"recording"===r.state&&i.stop(A),A()},this.getInternalRecorder=function(){return r},this.blob=null,this.getState=function(){return r&&r.state||"inactive"};var d=[];this.getAllStates=function(){return d},void 0===t.checkForInactiveTracks&&(t.checkForInactiveTracks=!1);i=this;!function o(){if(r&&!1!==t.checkForInactiveTracks)return!1===function(){if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}()?(t.disableLogs||console.log("MediaStream seems stopped."),void i.stop()):void setTimeout(o,1e3)}(),this.name="MediaStreamRecorder",this.toString=function(){return this.name}}function R(e,t){if(!w(e,"audio").length)throw"Your stream has no audio tracks.";var o,r=this,s=[],a=[],n=!1,A=0,d=2,c=(t=t||{}).desiredSampRate;function u(){if(!1===t.checkForInactiveTracks)return!0;if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}function h(e,t){function i(e,t){var i,o=e.numberOfAudioChannels,r=e.leftBuffers.slice(0),s=e.rightBuffers.slice(0),a=e.sampleRate,n=e.internalInterleavedLength,A=e.desiredSampRate;function d(e,t,i){var o=Math.round(e.length*(t/i)),r=[],s=Number((e.length-1)/(o-1));r[0]=e[0];for(var a=1;a96e3)&&(t.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),t.disableLogs||t.desiredSampRate&&console.log("Desired sample-rate: "+t.desiredSampRate);var y=!1;function v(){s=[],a=[],A=0,E=!1,n=!1,y=!1,p=null,r.leftchannel=s,r.rightchannel=a,r.numberOfAudioChannels=d,r.desiredSampRate=c,r.sampleRate=b,r.recordingLength=A,B={left:[],right:[],recordingLength:0}}function S(){o&&(o.onaudioprocess=null,o.disconnect(),o=null),m&&(m.disconnect(),m=null),v()}this.pause=function(){y=!0},this.resume=function(){if(!1===u())throw"Please make sure MediaStream is active.";if(!n)return t.disableLogs||console.log("Seems recording has been restarted."),void this.record();y=!1},this.clearRecordedData=function(){t.checkForInactiveTracks=!1,n&&this.stop(S),S()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var E=!1;o.onaudioprocess=function(e){if(!y)if(!1===u()&&(t.disableLogs||console.log("MediaStream seems stopped."),o.disconnect(),n=!1),n){E||(E=!0,t.onAudioProcessStarted&&t.onAudioProcessStarted(),t.initCallback&&t.initCallback());var i=e.inputBuffer.getChannelData(0),c=new Float32Array(i);if(s.push(c),2===d){var l=e.inputBuffer.getChannelData(1),h=new Float32Array(l);a.push(h)}A+=f,r.recordingLength=A,void 0!==t.timeSlice&&(B.recordingLength+=f,B.left.push(c),2===d&&B.right.push(h))}else m&&(m.disconnect(),m=null)},p.createMediaStreamDestination?o.connect(p.createMediaStreamDestination()):o.connect(p.destination),this.leftchannel=s,this.rightchannel=a,this.numberOfAudioChannels=d,this.desiredSampRate=c,this.sampleRate=b,r.recordingLength=A;var B={left:[],right:[],recordingLength:0};function C(){n&&"function"==typeof t.ondataavailable&&void 0!==t.timeSlice&&(B.left.length?(h({desiredSampRate:c,sampleRate:b,numberOfAudioChannels:d,internalInterleavedLength:B.recordingLength,leftBuffers:B.left,rightBuffers:1===d?[]:B.right},(function(e,i){var o=new Blob([i],{type:"audio/wav"});t.ondataavailable(o),setTimeout(C,t.timeSlice)})),B={left:[],right:[],recordingLength:0}):setTimeout(C,t.timeSlice))}}function k(e,t){if("undefined"==typeof html2canvas)throw"Please link: https://www.webrtc-experiment.com/screenshot.js";(t=t||{}).frameInterval||(t.frameInterval=10);var i=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach((function(e){e in document.createElement("canvas")&&(i=!0)}));var o,r,s,a=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),n=50,A=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);if(a&&A&&A[2]&&(n=parseInt(A[2],10)),a&&n<52&&(i=!1),t.useWhammyRecorder&&(i=!1),i)if(t.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),e instanceof HTMLCanvasElement)o=e;else{if(!(e instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";o=e.canvas}else navigator.mozGetUserMedia&&(t.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));this.record=function(){if(s=!0,i&&!t.useWhammyRecorder){var e;"captureStream"in o?e=o.captureStream(25):"mozCaptureStream"in o?e=o.mozCaptureStream(25):"webkitCaptureStream"in o&&(e=o.webkitCaptureStream(25));try{var a=new f;a.addTrack(w(e,"video")[0]),e=a}catch(e){}if(!e)throw"captureStream API are NOT available.";(r=new C(e,{mimeType:t.mimeType||"video/webm"})).record()}else h.frames=[],u=(new Date).getTime(),l();t.initCallback&&t.initCallback()},this.getWebPImages=function(i){if("canvas"===e.nodeName.toLowerCase()){var o=h.frames.length;h.frames.forEach((function(e,i){var r=o-i;t.disableLogs||console.log(r+"/"+o+" frames remaining"),t.onEncodingCallback&&t.onEncodingCallback(r,o);var s=e.image.toDataURL("image/webp",1);h.frames[i].image=s})),t.disableLogs||console.log("Generating WebM"),i()}else i()},this.stop=function(e){s=!1;var o=this;i&&r?r.stop(e):this.getWebPImages((function(){h.compile((function(i){t.disableLogs||console.log("Recording finished!"),o.blob=i,o.blob.forEach&&(o.blob=new Blob([],{type:"video/webm"})),e&&e(o.blob),h.frames=[]}))}))};var d=!1;function c(){h.frames=[],s=!1,d=!1}function l(){if(d)return u=(new Date).getTime(),setTimeout(l,500);if("canvas"===e.nodeName.toLowerCase()){var i=(new Date).getTime()-u;return u=(new Date).getTime(),h.frames.push({image:(o=document.createElement("canvas"),r=o.getContext("2d"),o.width=e.width,o.height=e.height,r.drawImage(e,0,0),o),duration:i}),void(s&&setTimeout(l,t.frameInterval))}var o,r;html2canvas(e,{grabMouse:void 0===t.showMousePointer||t.showMousePointer,onrendered:function(e){var i=(new Date).getTime()-u;if(!i)return setTimeout(l,t.frameInterval);u=(new Date).getTime(),h.frames.push({image:e.toDataURL("image/webp",1),duration:i}),s&&setTimeout(l,t.frameInterval)}})}this.pause=function(){d=!0,r instanceof C&&r.pause()},this.resume=function(){d=!1,r instanceof C?r.resume():s||this.record()},this.clearRecordedData=function(){s&&this.stop(c),c()},this.name="CanvasRecorder",this.toString=function(){return this.name};var u=(new Date).getTime(),h=new I.Video(100)}function T(e,t){function i(e){e=void 0!==e?e:10;var t=(new Date).getTime()-A;return t?s?(A=(new Date).getTime(),setTimeout(i,100)):(A=(new Date).getTime(),n.paused&&n.play(),l.drawImage(n,0,0,c.width,c.height),d.frames.push({duration:t,image:c.toDataURL("image/webp")}),void(r||setTimeout(i,e,e))):setTimeout(i,e,e)}function o(e,t,i,o,r){var s=document.createElement("canvas");s.width=c.width,s.height=c.height;var a,n,A,d=s.getContext("2d"),l=[],u=-1===t,h=t&&t>0&&t<=e.length?t:e.length,p=0,m=0,g=0,f=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),b=i&&i>=0&&i<=1?i:0,y=o&&o>=0&&o<=1?o:0,v=!1;n=-1,A=(a={length:h,functionToLoop:function(t,i){var o,r,s,a=function(){!v&&s-o<=s*y||(u&&(v=!0),l.push(e[i])),t()};if(v)a();else{var n=new Image;n.onload=function(){d.drawImage(n,0,0,c.width,c.height);var e=d.getImageData(0,0,c.width,c.height);o=0,r=e.data.length,s=e.data.length/4;for(var t=0;t127)throw"TrackNumber > 127 not supported";return[128|e.trackNum,e.timecode>>8,255&e.timecode,t].map((function(e){return String.fromCharCode(e)})).join("")+e.frame}({discardable:0,frame:e.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(t)});return t+=e.duration,{data:i,id:163}})))}function i(e){for(var t=[];e>0;)t.push(255&e),e>>=8;return new Uint8Array(t.reverse())}function o(e){var t=[];e=(e.length%8?new Array(9-e.length%8).join("0"):"")+e;for(var i=0;i1?2*s[0].width:s[0].width;var n=1;3!==e&&4!==e||(n=2),5!==e&&6!==e||(n=3),7!==e&&8!==e||(n=4),9!==e&&10!==e||(n=5),r.height=s[0].height*n}else r.width=a.width||360,r.height=a.height||240;t&&t instanceof HTMLVideoElement&&u(t),s.forEach((function(e,t){u(e,t)})),setTimeout(l,a.frameInterval)}}function u(e,t){if(!o){var i=0,r=0,a=e.width,n=e.height;1===t&&(i=e.width),2===t&&(r=e.height),3===t&&(i=e.width,r=e.height),4===t&&(r=2*e.height),5===t&&(i=e.width,r=2*e.height),6===t&&(r=3*e.height),7===t&&(i=e.width,r=3*e.height),void 0!==e.stream.left&&(i=e.stream.left),void 0!==e.stream.top&&(r=e.stream.top),void 0!==e.stream.width&&(a=e.stream.width),void 0!==e.stream.height&&(n=e.stream.height),s.drawImage(e,i,r,a,n),"function"==typeof e.stream.onRender&&e.stream.onRender(s,i,r,a,n,t)}}function h(e){var i=document.createElement("video");return function(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}(e,i),i.className=t,i.muted=!0,i.volume=0,i.width=e.width||a.width||360,i.height=e.height||a.height||240,i.play(),i}function p(t){i=[],(t=t||e).forEach((function(e){if(e.getTracks().filter((function(e){return"video"===e.kind})).length){var t=h(e);t.stream=e,i.push(t)}}))}void 0!==n?c.AudioContext=n:"undefined"!=typeof webkitAudioContext&&(c.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){l()},this.appendStreams=function(t){if(!t)throw"First parameter is required.";t instanceof Array||(t=[t]),t.forEach((function(t){var o=new d;if(t.getTracks().filter((function(e){return"video"===e.kind})).length){var r=h(t);r.stream=t,i.push(r),o.addTrack(t.getTracks().filter((function(e){return"video"===e.kind}))[0])}if(t.getTracks().filter((function(e){return"audio"===e.kind})).length){var s=a.audioContext.createMediaStreamSource(t);a.audioDestination=a.audioContext.createMediaStreamDestination(),s.connect(a.audioDestination),o.addTrack(a.audioDestination.stream.getTracks().filter((function(e){return"audio"===e.kind}))[0])}e.push(o)}))},this.releaseStreams=function(){i=[],o=!0,a.gainNode&&(a.gainNode.disconnect(),a.gainNode=null),a.audioSources.length&&(a.audioSources.forEach((function(e){e.disconnect()})),a.audioSources=[]),a.audioDestination&&(a.audioDestination.disconnect(),a.audioDestination=null),a.audioContext&&a.audioContext.close(),a.audioContext=null,s.clearRect(0,0,r.width,r.height),r.stream&&(r.stream.stop(),r.stream=null)},this.resetVideoStreams=function(e){!e||e instanceof Array||(e=[e]),p(e)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=function(){o=!1;var t=function(){var e;p(),"captureStream"in r?e=r.captureStream():"mozCaptureStream"in r?e=r.mozCaptureStream():a.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var t=new d;return e.getTracks().filter((function(e){return"video"===e.kind})).forEach((function(e){t.addTrack(e)})),r.stream=t,t}(),i=function(){c.AudioContextConstructor||(c.AudioContextConstructor=new c.AudioContext);a.audioContext=c.AudioContextConstructor,a.audioSources=[],!0===a.useGainNode&&(a.gainNode=a.audioContext.createGain(),a.gainNode.connect(a.audioContext.destination),a.gainNode.gain.value=0);var t=0;if(e.forEach((function(e){if(e.getTracks().filter((function(e){return"audio"===e.kind})).length){t++;var i=a.audioContext.createMediaStreamSource(e);!0===a.useGainNode&&i.connect(a.gainNode),a.audioSources.push(i)}})),!t)return;return a.audioDestination=a.audioContext.createMediaStreamDestination(),a.audioSources.forEach((function(e){e.connect(a.audioDestination)})),a.audioDestination.stream}();return i&&i.getTracks().filter((function(e){return"audio"===e.kind})).forEach((function(e){t.addTrack(e)})),e.forEach((function(e){e.fullcanvas})),t}}function L(e,t){e=e||[];var i,o,r=this;(t=t||{elementClass:"multi-streams-mixer",mimeType:"video/webm",video:{width:360,height:240}}).frameInterval||(t.frameInterval=10),t.video||(t.video={}),t.video.width||(t.video.width=360),t.video.height||(t.video.height=240),this.record=function(){var r;i=new j(e,t.elementClass||"multi-streams-mixer"),(r=[],e.forEach((function(e){w(e,"video").forEach((function(e){r.push(e)}))})),r).length&&(i.frameInterval=t.frameInterval||10,i.width=t.video.width||360,i.height=t.video.height||240,i.startDrawingFrames()),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()),(o=new C(i.getMixedStream(),t)).record()},this.stop=function(e){o&&o.stop((function(t){r.blob=t,e(t),r.clearRecordedData()}))},this.pause=function(){o&&o.pause()},this.resume=function(){o&&o.resume()},this.clearRecordedData=function(){o&&(o.clearRecordedData(),o=null),i&&(i.releaseStreams(),i=null)},this.addStreams=function(r){if(!r)throw"First parameter is required.";r instanceof Array||(r=[r]),e.concat(r),o&&i&&(i.appendStreams(r),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()))},this.resetVideoStreams=function(e){i&&(!e||e instanceof Array||(e=[e]),i.resetVideoStreams(e))},this.getMixer=function(){return i},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function F(e,t){var i,o,r;function s(){return new ReadableStream({start:function(o){var r=document.createElement("canvas"),s=document.createElement("video"),a=!0;s.srcObject=e,s.muted=!0,s.height=t.height,s.width=t.width,s.volume=0,s.onplaying=function(){r.width=t.width,r.height=t.height;var e=r.getContext("2d"),n=1e3/t.frameRate,A=setInterval((function(){if(i&&(clearInterval(A),o.close()),a&&(a=!1,t.onVideoProcessStarted&&t.onVideoProcessStarted()),e.drawImage(s,0,0),"closed"!==o._controlledReadableStream.state)try{o.enqueue(e.getImageData(0,0,t.width,t.height))}catch(e){}}),n)},s.play()}})}function a(e,A){if(!t.workerPath&&!A)return i=!1,void fetch("https://unpkg.com/webm-wasm@latest/dist/webm-worker.js").then((function(t){t.arrayBuffer().then((function(t){a(e,t)}))}));if(!t.workerPath&&A instanceof ArrayBuffer){var d=new Blob([A],{type:"text/javascript"});t.workerPath=l.createObjectURL(d)}t.workerPath||console.error("workerPath parameter is missing."),(o=new Worker(t.workerPath)).postMessage(t.webAssemblyPath||"https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm"),o.addEventListener("message",(function(e){"READY"===e.data?(o.postMessage({width:t.width,height:t.height,bitrate:t.bitrate||1200,timebaseDen:t.frameRate||30,realtime:t.realtime}),s().pipeTo(new WritableStream({write:function(e){i?console.error("Got image, but recorder is finished!"):o.postMessage(e.data.buffer,[e.data.buffer])}}))):e.data&&(r||n.push(e.data))}))}"undefined"!=typeof ReadableStream&&"undefined"!=typeof WritableStream||console.error("Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js"),(t=t||{}).width=t.width||640,t.height=t.height||480,t.frameRate=t.frameRate||30,t.bitrate=t.bitrate||1200,t.realtime=t.realtime||!0,this.record=function(){n=[],r=!1,this.blob=null,a(e),"function"==typeof t.initCallback&&t.initCallback()},this.pause=function(){r=!0},this.resume=function(){r=!1};var n=[];this.stop=function(e){i=!0;var t=this;!function(e){o?(o.addEventListener("message",(function(t){null===t.data&&(o.terminate(),o=null,e&&e())})),o.postMessage(null)):e&&e()}((function(){t.blob=new Blob(n,{type:"video/webm"}),e(t.blob)}))},this.name="WebAssemblyRecorder",this.toString=function(){return this.name},this.clearRecordedData=function(){n=[],r=!1,this.blob=null},this.blob=null}i.DiskStorage=x,i.GifRecorder=D,i.MultiStreamRecorder=L,i.RecordRTCPromisesHandler=function(e,t){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if(void 0===e)throw'First argument "MediaStream" is required.';var o=this;o.recordRTC=new i(e,t),this.startRecording=function(){return new Promise((function(e,t){try{o.recordRTC.startRecording(),e()}catch(e){t(e)}}))},this.stopRecording=function(){return new Promise((function(e,t){try{o.recordRTC.stopRecording((function(i){o.blob=o.recordRTC.getBlob(),o.blob&&o.blob.size?e(i):t("Empty blob.",o.blob)}))}catch(e){t(e)}}))},this.pauseRecording=function(){return new Promise((function(e,t){try{o.recordRTC.pauseRecording(),e()}catch(e){t(e)}}))},this.resumeRecording=function(){return new Promise((function(e,t){try{o.recordRTC.resumeRecording(),e()}catch(e){t(e)}}))},this.getDataURL=function(e){return new Promise((function(e,t){try{o.recordRTC.getDataURL((function(t){e(t)}))}catch(e){t(e)}}))},this.getBlob=function(){return new Promise((function(e,t){try{e(o.recordRTC.getBlob())}catch(e){t(e)}}))},this.getInternalRecorder=function(){return new Promise((function(e,t){try{e(o.recordRTC.getInternalRecorder())}catch(e){t(e)}}))},this.reset=function(){return new Promise((function(e,t){try{e(o.recordRTC.reset())}catch(e){t(e)}}))},this.destroy=function(){return new Promise((function(e,t){try{e(o.recordRTC.destroy())}catch(e){t(e)}}))},this.getState=function(){return new Promise((function(e,t){try{e(o.recordRTC.getState())}catch(e){t(e)}}))},this.blob=null,this.version="5.6.2"},i.WebAssemblyRecorder=F}));class Xe extends De{constructor(e){super(),this.player=e,this.fileName="",this.fileType=e._opt.recordType||c,this.isRecording=!1,this.recordingTimestamp=0,this.recordingInterval=null,e.debug.log("Recorder","init")}destroy(){this._reset(),this.player.debug.log("Recorder","destroy")}setFileName(e,t){this.fileName=e,d!==t&&c!==t||(this.fileType=t)}get recording(){return this.isRecording}get recordTime(){return this.recordingTimestamp}startRecord(){const e=this.player.debug,t={type:"video",mimeType:"video/webm;codecs=h264",onTimeStamp:t=>{e.log("Recorder","record timestamp :"+t)},disableLogs:!this.player._opt.debug};try{const e=this.player.video.$videoElement.captureStream(25);if(this.player.audio&&this.player.audio.mediaStreamAudioDestinationNode&&this.player.audio.mediaStreamAudioDestinationNode.stream&&!this.player.audio.isStateSuspended()&&this.player.audio.hasAudio&&this.player._opt.hasAudio){const t=this.player.audio.mediaStreamAudioDestinationNode.stream;if(t.getAudioTracks().length>0){const i=t.getAudioTracks()[0];i&&i.enabled&&e.addTrack(i)}}this.recorder=Ye(e,t)}catch(t){e.error("Recorder","startRecord error",t),this.emit(x.recordCreateError)}this.recorder&&(this.isRecording=!0,this.player.emit(x.recording,!0),this.recorder.startRecording(),e.log("Recorder","start recording"),this.player.emit(x.recordStart),this.recordingInterval=window.setInterval((()=>{this.recordingTimestamp+=1,this.player.emit(x.recordingTimestamp,this.recordingTimestamp)}),1e3))}stopRecordAndSave(){this.recorder&&this.isRecording&&this.recorder.stopRecording((()=>{this.player.debug.log("Recorder","stop recording"),this.player.emit(x.recordEnd);const e=(this.fileName||be())+"."+(this.fileType||c);Ue(this.recorder.getBlob(),e),this._reset(),this.player.emit(x.recording,!1)}))}_reset(){this.isRecording=!1,this.recordingTimestamp=0,this.recorder&&(this.recorder.destroy(),this.recorder=null),this.fileName=null,this.recordingInterval&&clearInterval(this.recordingInterval),this.recordingInterval=null}}class qe{constructor(e){return new(qe.getLoaderFactory())(e)}static getLoaderFactory(){return Xe}}class Ze{constructor(e){this.player=e,this.decoderWorker=new Worker(e._opt.decoder),this._initDecoderWorker(),e.debug.log("decoderWorker","init")}destroy(){this.decoderWorker.postMessage({cmd:T}),this.decoderWorker.terminate(),this.decoderWorker=null,this.player.debug.log("decoderWorker","destroy")}_initDecoderWorker(){const{debug:e,events:{proxy:t}}=this.player;this.decoderWorker.onmessage=t=>{const i=t.data;switch(i.cmd){case u:e.log("decoderWorker","onmessage:",u),this.player.loaded||this.player.emit(x.load),this.player.emit(x.decoderWorkerInit),this._initWork();break;case b:e.log("decoderWorker","onmessage:",b,i.code),this.player._times.decodeStart||(this.player._times.decodeStart=be()),this.player.video.updateVideoInfo({encTypeCode:i.code});break;case f:e.log("decoderWorker","onmessage:",f,i.code),this.player.audio&&this.player.audio.updateAudioInfo({encTypeCode:i.code});break;case h:if(e.log("decoderWorker","onmessage:",h,`width:${i.w},height:${i.h}`),this.player.video.updateVideoInfo({width:i.w,height:i.h}),!this.player._opt.openWebglAlignment&&i.w/2%4!=0)return void this.player.emit(j.webglAlignmentError);this.player.video.initCanvasViewSize();break;case g:e.log("decoderWorker","onmessage:",g,`channels:${i.channels},sampleRate:${i.sampleRate}`),this.player.audio&&(this.player.audio.updateAudioInfo(i),this.player.audio.initScriptNode(i));break;case p:this.player.handleRender(),this.player.video.render(i),this.player.emit(x.timeUpdate,i.ts),this.player.updateStats({fps:!0,ts:i.ts,buf:i.delay}),this.player._times.videoStart||(this.player._times.videoStart=be(),this.player.handlePlayToRenderTimes());break;case m:this.player.playing&&this.player.audio&&this.player.audio.play(i.buffer,i.ts);break;case y:i.message&&-1!==i.message.indexOf(v)&&this.player.emitError(j.wasmDecodeError);break;default:this.player[i.cmd]&&this.player[i.cmd](i)}}}_initWork(){const e={debug:this.player._opt.debug,useOffscreen:this.player._opt.useOffscreen,useWCS:this.player._opt.useWCS,videoBuffer:this.player._opt.videoBuffer,videoBufferDelay:this.player._opt.videoBufferDelay,openWebglAlignment:this.player._opt.openWebglAlignment};this.decoderWorker.postMessage({cmd:C,opt:JSON.stringify(e),sampleRate:this.player.audio&&this.player.audio.audioContext.sampleRate||0})}decodeVideo(e,t,i){const o={type:S,ts:Math.max(t,0),isIFrame:i};this.decoderWorker.postMessage({cmd:R,buffer:e,options:o},[e.buffer])}decodeAudio(e,t){this.player._opt.useWCS||this.player._opt.useMSE?this._decodeAudioNoDelay(e,t):this._decodeAudio(e,t)}_decodeAudio(e,t){const i={type:w,ts:Math.max(t,0)};this.decoderWorker.postMessage({cmd:R,buffer:e,options:i},[e.buffer])}_decodeAudioNoDelay(e,t){this.decoderWorker.postMessage({cmd:k,buffer:e,ts:Math.max(t,0)},[e.buffer])}updateWorkConfig(e){this.decoderWorker.postMessage({cmd:I,key:e.key,value:e.value})}}class Ke extends De{constructor(e){super(),this.player=e,this.stopId=null,this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.initInterval()}destroy(){this.stopId&&(clearInterval(this.stopId),this.stopId=null),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.off(),this.player.debug.log("CommonDemux","destroy")}getDelay(e){if(!e)return-1;if(this.firstTimestamp){if(e){const t=Date.now()-this.startTimestamp,i=e-this.firstTimestamp;this.delay=t>=i?t-i:i-t}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay}resetDelay(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1}initInterval(){this.player.debug.log("common dumex","init Interval");let e=()=>{let e;const t=this.player._opt.videoBuffer,i=this.player._opt.videoBufferDelay;if(this.player._opt.useMSE&&this.player.mseDecoder&&this.player.mseDecoder.getSourceBufferUpdating())this.player.debug.warn("CommonDemux",`_loop getSourceBufferUpdating is true and bufferList length is ${this.bufferList.length}`);else if(this.bufferList.length)if(this.dropping){for(e=this.bufferList.shift(),e.type===w&&0===e.payload[1]&&this._doDecoderDecode(e);!e.isIFrame&&this.bufferList.length;)e=this.bufferList.shift(),e.type===w&&0===e.payload[1]&&this._doDecoderDecode(e);e.isIFrame&&this.getDelay(e.ts)<=Math.min(t,200)&&(this.dropping=!1,this._doDecoderDecode(e))}else e=this.bufferList[0],-1===this.getDelay(e.ts)?(this.bufferList.shift(),this._doDecoderDecode(e)):this.delay>t+i?(this.resetDelay(),this.dropping=!0):(e=this.bufferList[0],this.getDelay(e.ts)>t&&(this.bufferList.shift(),this._doDecoderDecode(e)))};e(),this.stopId=setInterval(e,10)}_doDecode(e,t,i,o,r){const s=this.player;let a={ts:i,cts:r,type:t,isIFrame:!1};s._opt.useWCS&&!s._opt.useOffscreen||s._opt.useMSE?(t===S&&(a.isIFrame=o),this.pushBuffer(e,a)):t===S?s.decoderWorker&&s.decoderWorker.decodeVideo(e,i,o):t===w&&s._opt.hasAudio&&s.decoderWorker&&s.decoderWorker.decodeAudio(e,i)}_doDecoderDecode(e){const t=this.player,{webcodecsDecoder:i,mseDecoder:o}=t;e.type===w?t._opt.hasAudio&&t.decoderWorker&&t.decoderWorker.decodeAudio(e.payload,e.ts):e.type===S&&(t._opt.useWCS&&!t._opt.useOffscreen?i.decodeVideo(e.payload,e.ts,e.isIFrame):t._opt.useMSE&&o.decodeVideo(e.payload,e.ts,e.isIFrame,e.cts))}pushBuffer(e,t){t.type===w?this.bufferList.push({ts:t.ts,payload:e,type:w}):t.type===S&&this.bufferList.push({ts:t.ts,cts:t.cts,payload:e,type:S,isIFrame:t.isIFrame})}close(){}_decodeEnhancedH265Video(e,t){const i=e[0],o=48&i,r=15&i,s=e.slice(1,5),a=new ArrayBuffer(4),n=new Uint32Array(a),A="a"==String.fromCharCode(s[0]);if(r===de){if(o===ue){const t=e.slice(5);if(!A){const e=new Uint8Array(5+t.length);e.set([28,0,0,0,0],0),e.set(t,5),this._doDecode(e,S,0,!0,0)}}}else if(r===ce){let i=e,r=0;const s=o===ue;if(!A){n[0]=e[4],n[1]=e[3],n[2]=e[2],n[3]=0,r=n[0];i=xe(e.slice(8),s),this._doDecode(i,S,t,s,r)}}else if(r===le){const i=o===ue;let r=xe(e.slice(5),i);this._doDecode(r,S,t,i,0)}}_isEnhancedH265Header(e){return 128==(128&e)}}class _e extends Ke{constructor(e){super(e),this.input=this._inputFlv(),this.flvDemux=this.dispatchFlvData(this.input),e.debug.log("FlvDemux","init")}destroy(){super.destroy(),this.input=null,this.flvDemux=null,this.player.debug.log("FlvDemux","destroy")}dispatch(e){this.flvDemux(e)}*_inputFlv(){yield 9;const e=new ArrayBuffer(4),t=new Uint8Array(e),i=new Uint32Array(e),o=this.player;for(;;){t[3]=0;const e=yield 15,r=e[4];t[0]=e[7],t[1]=e[6],t[2]=e[5];const s=i[0];t[0]=e[10],t[1]=e[9],t[2]=e[8];let a=i[0];16777215===a&&(t[3]=e[11],a=i[0]);const n=yield s;switch(r){case E:o._opt.hasAudio&&(o.updateStats({abps:n.byteLength}),n.byteLength>0&&this._doDecode(n,w,a));break;case B:if(o._times.demuxStart||(o._times.demuxStart=be()),o._opt.hasVideo){o.updateStats({vbps:n.byteLength});const e=n[0];if(this._isEnhancedH265Header(e))this._decodeEnhancedH265Video(n,a);else{const e=n[0]>>4==1;if(n.byteLength>0){i[0]=n[4],i[1]=n[3],i[2]=n[2],i[3]=0;let t=i[0];this._doDecode(n,S,a,e,t)}}}}}}dispatchFlvData(e){let t=e.next(),i=null;return o=>{let r=new Uint8Array(o);if(i){let e=new Uint8Array(i.length+r.length);e.set(i),e.set(r,i.length),r=e,i=null}for(;r.length>=t.value;){let i=r.slice(t.value);t=e.next(r.slice(0,t.value)),r=i}r.length>0&&(i=r)}}close(){this.input&&this.input.return(null)}}class $e extends Ke{constructor(e){super(e),e.debug.log("M7sDemux","init")}destroy(){super.destroy(),this.player.debug.log("M7sDemux","destroy"),this.player=null}dispatch(e){const t=this.player,i=new DataView(e),o=i.getUint8(0),r=i.getUint32(1,!1),s=new ArrayBuffer(4),a=new Uint32Array(s);switch(o){case w:if(t._opt.hasAudio){const i=new Uint8Array(e,5);t.updateStats({abps:i.byteLength}),i.byteLength>0&&this._doDecode(i,o,r)}break;case S:if(t._opt.hasVideo)if(t._times.demuxStart||(t._times.demuxStart=be()),i.byteLength>5){const s=new Uint8Array(e,5),n=s[0];if(this._isEnhancedH265Header(n))this._decodeEnhancedH265Video(s,r);else{const e=i.getUint8(5)>>4==1;t.updateStats({vbps:s.byteLength}),a[0]=s[4],a[1]=s[3],a[2]=s[2],a[3]=0;let n=a[0];this._doDecode(s,o,r,e,n)}}else this.player.debug.warn("M7sDemux","dispatch","dv byteLength is",i.byteLength)}}}class et{constructor(e){return new(et.getLoaderFactory(e._opt.demuxType))(e)}static getLoaderFactory(e){return e===A?$e:e===n?_e:void 0}}class tt{constructor(e){this.TAG="ExpGolomb",this._buffer=e,this._buffer_index=0,this._total_bytes=e.byteLength,this._total_bits=8*e.byteLength,this._current_word=0,this._current_word_bits_left=0}destroy(){this._buffer=null}_fillCurrentWord(){let e=this._total_bytes-this._buffer_index,t=Math.min(4,e),i=new Uint8Array(4);i.set(this._buffer.subarray(this._buffer_index,this._buffer_index+t)),this._current_word=new DataView(i.buffer).getUint32(0,!1),this._buffer_index+=t,this._current_word_bits_left=8*t}readBits(e){if(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 o=Math.min(i,this._current_word_bits_left),r=this._current_word>>>32-o;return this._current_word<<=o,this._current_word_bits_left-=o,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)}}class it{static _ebsp2rbsp(e){let t=e,i=t.byteLength,o=new Uint8Array(i),r=0;for(let e=0;e=2&&3===t[e]&&0===t[e-1]&&0===t[e-2]||(o[r]=t[e],r++);return new Uint8Array(o.buffer,0,r)}static parseSPS(e){let t=it._ebsp2rbsp(e),i=new tt(t);i.readByte();let o=i.readByte();i.readByte();let r=i.readByte();i.readUEG();let s=it.getProfileString(o),a=it.getLevelString(r),n=1,A=420,d=[0,420,422,444],c=8;if((100===o||110===o||122===o||244===o||44===o||83===o||86===o||118===o||128===o||138===o||144===o)&&(n=i.readUEG(),3===n&&i.readBits(1),n<=3&&(A=d[n]),c=i.readUEG()+8,i.readUEG(),i.readBits(1),i.readBool())){let e=3!==n?8:12;for(let t=0;t0&&e<16?(v=t[e-1],w=o[e-1]):255===e&&(v=i.readByte()<<8|i.readByte(),w=i.readByte()<<8|i.readByte())}if(i.readBool()&&i.readBool(),i.readBool()&&(i.readBits(4),i.readBool()&&i.readBits(24)),i.readBool()&&(i.readUEG(),i.readUEG()),i.readBool()){let e=i.readBits(32),t=i.readBits(32);E=i.readBool(),B=t,C=2*e,S=B/C}}let R=1;1===v&&1===w||(R=v/w);let k=0,T=0;if(0===n)k=1,T=2-m;else{k=3===n?1:2,T=(1===n?2:1)*(2-m)}let I=16*(h+1),x=16*(p+1)*(2-m);I-=(g+f)*k,x-=(b+y)*T;let D=Math.ceil(I*R);return i.destroy(),i=null,{profile_string:s,level_string:a,bit_depth:c,ref_frames:u,chroma_format:A,chroma_format_string:it.getChromaFormatString(A),frame_rate:{fixed:E,fps:S,fps_den:C,fps_num:B},sar_ratio:{width:v,height:w},codec_size:{width:I,height:x},present_size:{width:D,height:x}}}static _skipScalingList(e,t){let i=8,o=8,r=0;for(let s=0;s ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),void this.player.emit(j.webcodecsWidthOrHeightChange)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){const o=new EncodedVideoChunk({data:e.slice(5),timestamp:t,type:i?X:q});this.player.emit(x.timeUpdate,t);try{if(this.isDecodeStateClosed())return void this.player.debug.warn("Webcodecs","VideoDecoder isDecodeStateClosed true");this.decoder.decode(o)}catch(e){this.player.debug.error("Webcodecs","VideoDecoder",e),(-1!==e.toString().indexOf(re)||-1!==e.toString().indexOf(se))&&this.player.emitError(j.webcodecsDecodeError)}}else this.player.debug.warn("Webcodecs","VideoDecoder isDecodeFirstIIframe false")}else if(i&&0===e[1]){const t=15&e[0];if(this.player.video.updateVideoInfo({encTypeCode:t}),t===Q)return void this.emit(j.webcodecsH265NotSupport);this.player._times.decodeStart||(this.player._times.decodeStart=be());const i=function(e){let t=e.subarray(1,4),i="avc1.";for(let e=0;e<3;e++){let o=t[e].toString(16);o.length<2&&(o="0"+o),i+=o}return{codec:i,description:e}}(e.slice(5));this.decoder.configure(i),this.hasInit=!0}}isDecodeStateClosed(){return"closed"===this.decoder.state}}const st={play:"播放",pause:"暂停",audio:"",mute:"",screenshot:"截图",loading:"加载",fullscreen:"全屏",fullscreenExit:"退出全屏",record:"录制",recordStop:"停止录制"};var at=Object.keys(st).reduce(((e,t)=>(e[t]=`\n \n ${st[t]?`${st[t]}`:""}\n`,e)),{}),nt=(e,t)=>{const{events:{proxy:i}}=e,o=document.createElement("object");o.setAttribute("aria-hidden","true"),o.setAttribute("tabindex",-1),o.type="text/html",o.data="about:blank",ve(o,{display:"block",position:"absolute",top:"0",left:"0",height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"-1"});let r=e.width,s=e.height;i(o,"load",(()=>{i(o.contentDocument.defaultView,"resize",(()=>{e.width===r&&e.height===s||(r=e.width,s=e.height,e.emit(x.resize),n())}))})),e.$container.appendChild(o),e.on(x.destroy,(()=>{e.$container.removeChild(o)})),e.on(x.volumechange,(()=>{!function(e){if(0===e)ve(t.$volumeOn,"display","none"),ve(t.$volumeOff,"display","flex"),ve(t.$volumeHandle,"top","48px");else if(t.$volumeHandle&&t.$volumePanel){const i=we(t.$volumePanel,"height")||60,o=we(t.$volumeHandle,"height"),r=i-(i-o)*e-o;ve(t.$volumeHandle,"top",`${r}px`),ve(t.$volumeOn,"display","flex"),ve(t.$volumeOff,"display","none")}t.$volumePanelText&&(t.$volumePanelText.innerHTML=parseInt(100*e))}(e.volume)})),e.on(x.loading,(e=>{ve(t.$loading,"display",e?"flex":"none"),ve(t.$poster,"display","none"),e&&ve(t.$playBig,"display","none")}));const a=i=>{let o=!0===(r=i)||!1===r?i:e.fullscreen;var r;ve(t.$fullscreenExit,"display",o?"flex":"none"),ve(t.$fullscreen,"display",o?"none":"flex")},n=()=>{Be()&&t.$controls&&e._opt.useWebFullScreen&&setTimeout((()=>{if(e.fullscreen){let i=e.height/2-e.width+19,o=e.height/2-19;t.$controls.style.transform=`translateX(${-i}px) translateY(-${o}px) rotate(-90deg)`}else t.$controls.style.transform="translateX(0) translateY(0) rotate(0)"}),10)};try{me.on("change",a),e.events.destroys.push((()=>{me.off("change",a)}))}catch(e){}e.on(x.webFullscreen,(e=>{a(e),n()})),e.on(x.recording,(()=>{ve(t.$record,"display",e.recording?"none":"flex"),ve(t.$recordStop,"display",e.recording?"flex":"none"),ve(t.$recording,"display",e.recording?"flex":"none")})),e.on(x.recordingTimestamp,(e=>{t.$recordingTime&&(t.$recordingTime.innerHTML=function(e){var t;if(e>-1){var i=Math.floor(e/3600),o=Math.floor(e/60)%60,r=e%60;t=i<10?"0"+i+":":i+":",o<10&&(t+="0"),t+=o+":",(r=Math.round(r))<10&&(t+="0"),t+=r.toFixed(0)}return t}(e))})),e.on(x.playing,(e=>{ve(t.$play,"display",e?"none":"flex"),ve(t.$playBig,"display",e?"none":"block"),ve(t.$pause,"display",e?"flex":"none"),ve(t.$screenshot,"display",e?"flex":"none"),ve(t.$record,"display",e?"flex":"none"),ve(t.$qualityMenu,"display",e?"flex":"none"),ve(t.$volume,"display",e?"flex":"none"),a(),e||t.$speed&&(t.$speed.innerHTML=Ce(""))})),e.on(x.kBps,(e=>{const i=Ce(e);t.$speed&&(t.$speed.innerHTML=i)}))};function At(e,t){void 0===t&&(t={});var i=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===i&&o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}At('@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;transition:all .2s ease-in-out;-webkit-user-select:none;user-select:none;transition:width .5s ease-in}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKYklEQVRoQ+1Z+3NV1Rld397nXJIbIGBARTQgohGNQZJLEtFSMmpfan10aJ1OZzqd/jOd/g3t9AetD2KLCiigNFUgj/tIQoh1SqBRwVqNYgp53XvP2V9nped0Lpebl/LQmZ4ZZpjkZJ+99voe61tb8C1/5Fu+f/wfwPVm8DIG+vv7H1bVWufcp9baUefcWCqVKi5lo11dXV5NTc06EblPRNoAtABYqapD1tq9zrmelpaWaRHRpaxb6d3LAGSz2d+IyAbn3FljTG+xWEy3t7efW+yHuru7q621t3med7+qPgigGcCdAPIAuowxzyUSiaONjY2Fxa4533uVABwEsA3ARQDHAez1fb9769atn823kKrKyZMnVxUKhdtFJKWq3wWQAnAzgBoAH6vqQWvtH8nAUlmd69uXAcjlci+q6sMA1gL4BMB+Vd2fSCR6K4HYs2eP3bRp0zJjDN/f7Jzjphk2PPkN0YcDACOqekhVO5PJZPZqMvBLAI8BeATAagBnARwRkT97ntdXDmJ4eHj59PT0emPMVufcA9y8iNwBoA6AjQCEAE5dEwDpdPo2EXlQRJ4G8B0A6yImDqjqvnImstnsOlVtFZHvA9gJ4C4AfhnlLAJnABxW1T3V1dWZq8aAqppMJrM+AvE4gB8CuKGUCd/3jzU1NX3JuB8cHNwchuGjBKyq7QCWV4jXawcg/ng6nb7ZWrtTVX8C4CEAtxCEiLzBZAzD8ERNTc1YoVBY6ZxjtXkyYoDvxaETL3ftAfDLvb29t1prufnHohBZQxCqmmVJVNVjQRB8VF1dXeece0hVfxAlcD1wSZe/dgCy2Wy97/sz1topAIWpqambRKTDGPOsqu4AUAvgPICMiBxU1SMzMzMfJJPJG1SVYB+P6n8pE6xCpxebA8PDw4mJiYkqHqLnedPzldxKZfRXqvqliJwtFosjXEBVG0Xkp9wcgMYoLr4EMAjgDRE5PD09PVpTU1MXhiHrP6sY8+G2kjIaJ/HLCyXxiRMnbiwWi7cqk0zkbCqV+nzRfSCbzXay6ojISQDHVq5c+Y+JiYl1zrmnnHNPiwjre5yoFwAwnN6MQfi+v8bzvF0EoaqsYgw7wyokIm86515aCEAul9vinNtujHFBEKTb2tpOLQXApwA+EJHjzrnX8/l8jicbBAE3z4S+P+qs8ZrjERMHABxiOFVVVd2oqruMMT9WVTY2gjgXFYCXAfTNFxa5XI7sMRT57Nu+fXt6KQAosNj2uwB0iki3tXZ1GIbPAOA/hlCybMF/A8gxnBjnQRB86Ps+QbAZMrG3RlqIDfGlCxcu9OzatcsNDg5S4NWqqm+tpbgbb2pqmh4YGHjIOfczfoPvt7S0HF0qgDEROaKqPK1jUeKyzj8jIk1lDJQzsb8ExHrn3E4RmZUmqsqceWV0dLS3oaGhKp/P3yMid3N9Y8xnVKuFQoHgm0WEADwRefGrAPhYRP5CBoIg6BaRWmstw4EMUOhValYEEjNxwDl3yPf9j4MguMkYs9M5x80yPA9fvHhxqKamZo21ltKd+ULBNyoiB/L5fMbzvDuMMVQCy5xzf2ptbe1eKgPUP7MACoVCj+d5q4wxTwCIc2DFPMqUOdEP4HWWWM/zzhWLRXb2LSISOOeGkskkf7YhyitulKLvfRF5XkQOOeduFpEnVLVaRF5taWnpXSqAD6NG1VksFnuXCIDfIog0O7Yx5kgYhp8ZYyipYa39Ynx8fKa2trbBOccDeRbA7QCGVfX3IkLgdSLCUsxcey2VSvVdawD8XtwnWJ2YR2dqa2svnjt3jsrUiwAwJH8OYBMBAPgdN/xNAVCaE2855w4mk8m/UYVGM8RG6iwRoXznxDYLwDm3T0TWiAibZlJEXrseIVTKeJwTrzKcEonEaYIYGhpanc/nycCvRaRRVf8uIn+IBiiG0DcGAMF8QW3IzYVheKitrW2UP0yn048YY34BoDV655UwDF83xqyKc4A5cb0ZiNn4XFXfBfCC53lHtm3bNp7NZjm5dQCgHE+q6lFjzEHn3IqIgerrmcSVCgfdjTe5Kd/3M9PT0zO+76+PbBdK8DOq2kPpEZXRqq+aAx+xjLIPhGHYW9LIWPYoC+brA/O0CLhosnuHGkdV+4wxDC+OpRxlLyQSidGZmZnN1tonnXMJ+kjNzc0EVfGpZKtQC/2LjYzzK0VdJCWeiqrGffN04rm+w3mAQ00imtZo0bxFJpxzRycnJ8fr6uqqwzBU3/enpqamUiKyW0SoYjtTqRTL8JIA0E75K4A9xpjjFFwAqIXIAAGUi7n5Tp2/m4yaG4f9G6OXeUizboeI9J4+ffrT3bt3kyFkMpkHjDEssRKG4StLlRKcxCglqAD3MoRokVhr2fJ3A6CYK3cdFgLAuYGHwpLqAWDcU/9QwB02xuwLw/Dd1tZWgmJ1utcY8wgNBpbelpaWoaUwMCAiH3Hudc4dcc4Ne55H04oDCk+ldKBZaOPx78kAxdowLUsRIQBWn1nLRkTeJtu+7x+n28GJrFAo3Gmttc65kVQqRfCLC6FMJvPbSDWeofCanJz854oVK2hwcd79UVTyKL4Yz4t9ZiJfiALxqIgkVPVRAN8r8Z32s+aLSF8ikaCqTUxOTi6bmpqa7Ojo4N8vDkB/fz/dNYbRuLX2cw4YuVyuyhhzZxiG7SLCmZdT2UYArNOLeWjkciamOfaqqn5ijGmKGOXAE7sdbxtj9pY6gP8di+d2sS+rQl1dXVVr1651Y2NjrqOjg9UDXKSnp2d1IpHgpptVdbuI0DKnilwVzbzzAZm1VTgTR0NSfxAEN/i+z1mA1S2eCRgqByImepubm8cWOp1F39Awod57771ksVjkgH+3qpIpzrtbANy0QGLPAqC85ogYy2P6Tr7vP6iqnDViB5DNjjlBWdHb1tbGPjHns2gA8QpUkhs3blxrjOHGyQJ1zD2RhcIGV2nNS4ytVCrVIyKzJTM2zyIvlt4qq9MsE5W82HIkSwYQh1Qul1sJoF5EtkbOA9mgLGbFKl/3EgATExN9peHZ19e3ng5gpH8uYWIuVzwG8pUAxH+czWbpJqwPw/DeyMjaDoD/Z7MqrVIEMOvMOef2VLofKGMidsU5Qx+iig2CoGf58uXjjY2NE6UsfC0AXIgh1dDQQEeOecEEZ25QL3HKihveggCYY319fbdUYIJ9gobYc6p6prW1lU32f8/XBhCvxAGF10uqui262GNusGpRhvDhnM24fkFE0nMZW2TC8zzmAjs/c4ylukdVOa29H88SVySEyhMqm81yBKSpu4VMiMgOVaX0YCOcva4yxjw/3x0ZmcjlcrxnI5Ps+mtUdYTgwzD8sLwqXTEGSqtUfX09PR/aKIxldvAGOt0A3nHOvRwEwfEdO3ZMz1UbR0ZGlp0/f/4WEam31vL+4by19hQ7dPnNzhUHEG9qYGBgVRAEd0UNj2YYWThjjHmrUChk2tvbKfDmfHjX7Pt+te/7nAnYUKcqhd1VA8Dkrq+vXxcxQdnAewbOAb1BEAwtBCAq16azs3N2j5TalSTFVQMw3+leyd996wH8BxA4v3x6wGifAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:"";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}');class dt{constructor(e){var t;this.player=e,((e,t)=>{e._opt.hasControl&&e._opt.controlAutoHide?e.$container.classList.add("jessibuca-controls-show-auto-hide"):e.$container.classList.add("jessibuca-controls-show");const i=e._opt,o=i.operateBtns;e.$container.insertAdjacentHTML("beforeend",`\n ${i.background?`
`:""}\n
\n ${at.loading}\n ${i.loadingText?`
${i.loadingText}
`:""}\n
\n ${i.hasControl&&o.play?'
':""}\n ${i.hasControl?`\n
\n
\n
00:00:01
\n
${at.recordStop}
\n
\n `:""}\n ${i.hasControl?`\n
\n
\n
\n ${i.showBandwidth?'
':""}\n
\n
\n ${o.audio?`\n
\n ${at.audio}\n ${at.mute}\n
\n
\n
\n
\n
\n
\n
\n `:""}\n ${o.play?`
${at.play}
${at.pause}
`:""}\n ${o.screenshot?`
${at.screenshot}
`:""}\n ${o.record?`
${at.record}
${at.recordStop}
`:""}\n ${o.fullscreen?`
${at.fullscreen}
${at.fullscreenExit}
`:""}\n
\n
\n
\n `:""}\n\n `),Object.defineProperty(t,"$poster",{value:e.$container.querySelector(".jessibuca-poster")}),Object.defineProperty(t,"$loading",{value:e.$container.querySelector(".jessibuca-loading")}),Object.defineProperty(t,"$play",{value:e.$container.querySelector(".jessibuca-play")}),Object.defineProperty(t,"$playBig",{value:e.$container.querySelector(".jessibuca-play-big")}),Object.defineProperty(t,"$recording",{value:e.$container.querySelector(".jessibuca-recording")}),Object.defineProperty(t,"$recordingTime",{value:e.$container.querySelector(".jessibuca-recording-time")}),Object.defineProperty(t,"$recordingStop",{value:e.$container.querySelector(".jessibuca-recording-stop")}),Object.defineProperty(t,"$pause",{value:e.$container.querySelector(".jessibuca-pause")}),Object.defineProperty(t,"$controls",{value:e.$container.querySelector(".jessibuca-controls")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$volume",{value:e.$container.querySelector(".jessibuca-volume")}),Object.defineProperty(t,"$volumePanelWrap",{value:e.$container.querySelector(".jessibuca-volume-panel-wrap")}),Object.defineProperty(t,"$volumePanelText",{value:e.$container.querySelector(".jessibuca-volume-panel-text")}),Object.defineProperty(t,"$volumePanel",{value:e.$container.querySelector(".jessibuca-volume-panel")}),Object.defineProperty(t,"$volumeHandle",{value:e.$container.querySelector(".jessibuca-volume-panel-handle")}),Object.defineProperty(t,"$volumeOn",{value:e.$container.querySelector(".jessibuca-icon-audio")}),Object.defineProperty(t,"$volumeOff",{value:e.$container.querySelector(".jessibuca-icon-mute")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreenExit",{value:e.$container.querySelector(".jessibuca-fullscreen-exit")}),Object.defineProperty(t,"$record",{value:e.$container.querySelector(".jessibuca-record")}),Object.defineProperty(t,"$recordStop",{value:e.$container.querySelector(".jessibuca-record-stop")}),Object.defineProperty(t,"$screenshot",{value:e.$container.querySelector(".jessibuca-screenshot")}),Object.defineProperty(t,"$speed",{value:e.$container.querySelector(".jessibuca-speed")})})(e,this),t=this,Object.defineProperty(t,"controlsRect",{get:()=>t.$controls.getBoundingClientRect()}),nt(e,this),((e,t)=>{const{events:{proxy:i},debug:o}=e;function r(e){const{bottom:i,height:o}=t.$volumePanel.getBoundingClientRect(),{height:r}=t.$volumeHandle.getBoundingClientRect();return ye(i-e.y-r/2,0,o-r/2)/(o-r)}if(i(window,["click","contextmenu"],(i=>{i.composedPath().indexOf(e.$container)>-1?t.isFocus=!0:t.isFocus=!1})),i(window,"orientationchange",(()=>{setTimeout((()=>{e.resize()}),300)})),i(t.$controls,"click",(e=>{e.stopPropagation()})),i(t.$pause,"click",(t=>{e.pause()})),i(t.$play,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$playBig,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$volume,"mouseover",(()=>{t.$volumePanelWrap.classList.add("jessibuca-volume-panel-wrap-show")})),i(t.$volume,"mouseout",(()=>{t.$volumePanelWrap.classList.remove("jessibuca-volume-panel-wrap-show")})),i(t.$volumeOn,"click",(i=>{i.stopPropagation(),ve(t.$volumeOn,"display","none"),ve(t.$volumeOff,"display","block");const o=e.volume;e.volume=0,e._lastVolume=o})),i(t.$volumeOff,"click",(i=>{i.stopPropagation(),ve(t.$volumeOn,"display","block"),ve(t.$volumeOff,"display","none"),e.volume=e.lastVolume||.5})),i(t.$screenshot,"click",(t=>{t.stopPropagation(),e.video.screenshot()})),i(t.$volumePanel,"click",(t=>{t.stopPropagation(),e.volume=r(t)})),i(t.$volumeHandle,"mousedown",(()=>{t.isVolumeDroging=!0})),i(t.$volumeHandle,"mousemove",(i=>{t.isVolumeDroging&&(e.volume=r(i))})),i(document,"mouseup",(()=>{t.isVolumeDroging&&(t.isVolumeDroging=!1)})),i(t.$record,"click",(t=>{t.stopPropagation(),e.recording=!0})),i(t.$recordStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$recordingStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$fullscreen,"click",(t=>{t.stopPropagation(),e.fullscreen=!0})),i(t.$fullscreenExit,"click",(t=>{t.stopPropagation(),e.fullscreen=!1})),e._opt.hasControl&&e._opt.controlAutoHide){i(e.$container,"mouseover",(()=>{e.fullscreen||(ve(t.$controls,"display","block"),r())})),i(e.$container,"mousemove",(()=>{e.$container&&t.$controls&&(e.fullscreen,"none"===t.$controls.style.display&&(ve(t.$controls,"display","block"),r()))})),i(e.$container,"mouseout",(()=>{s(),ve(t.$controls,"display","none")}));let o=null;const r=()=>{s(),o=setTimeout((()=>{ve(t.$controls,"display","none")}),5e3)},s=()=>{o&&(clearTimeout(o),o=null)}}})(e,this),e._opt.hotKey&&((e,t)=>{const{events:{proxy:i}}=e,o={};function r(e,t){o[e]?o[e].push(t):o[e]=[t]}r(te,(()=>{e.fullscreen&&(e.fullscreen=!1)})),r(ie,(()=>{e.volume+=.05})),r(oe,(()=>{e.volume-=.05})),i(window,"keydown",(e=>{if(t.isFocus){const t=document.activeElement.tagName.toUpperCase(),i=document.activeElement.getAttribute("contenteditable");if("INPUT"!==t&&"TEXTAREA"!==t&&""!==i&&"true"!==i){const t=o[e.keyCode];t&&(e.preventDefault(),t.forEach((e=>e())))}}}))})(e,this),this.player.debug.log("Control","init")}destroy(){if(this.$poster){if(!Ie(this.$poster)){const e=this.player.$container.querySelector(".jessibuca-poster");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$loading){if(!Ie(this.$loading)){const e=this.player.$container.querySelector(".jessibuca-loading");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$controls){if(!Ie(this.$controls)){const e=this.player.$container.querySelector(".jessibuca-controls");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$recording){if(!Ie(this.$recording)){const e=this.player.$container.querySelector(".jessibuca-recording");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$playBig){if(!Ie(this.$playBig)){const e=this.player.$container.querySelector(".jessibuca-play-big");e&&this.player.$container&&this.player.$container.removeChild(e)}}this.player.debug.log("control","destroy")}autoSize(){const e=this.player;e.$container.style.padding="0 0";const t=e.width,i=e.height,o=t/i,r=e.video.$videoElement.width/e.video.$videoElement.height;if(o>r){const o=(t-i*r)/2;e.$container.style.padding=`0 ${o}px`}else{const o=(i-t/r)/2;e.$container.style.padding=`${o}px 0`}}}At(".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}");class ct{static init(){ct.types={avc1:[],avcC:[],hvc1:[],hvcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]};for(let e in ct.types)ct.types.hasOwnProperty(e)&&(ct.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);let e=ct.constants={};e.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),e.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),e.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSC=e.STCO=e.STTS,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),e.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),e.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])}static box(e){let t=8,i=null,o=Array.prototype.slice.call(arguments,1),r=o.length;for(let e=0;e>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);let s=8;for(let e=0;e>>24&255,e>>>16&255,e>>>8&255,255&e,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))}static trak(e){return ct.box(ct.types.trak,ct.tkhd(e),ct.mdia(e))}static tkhd(e){let t=e.id,i=e.duration,o=e.presentWidth,r=e.presentHeight;return ct.box(ct.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,o>>>8&255,255&o,0,0,r>>>8&255,255&r,0,0]))}static mdia(e){return ct.box(ct.types.mdia,ct.mdhd(e),ct.hdlr(e),ct.minf(e))}static mdhd(e){let t=e.timescale,i=e.duration;return ct.box(ct.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,85,196,0,0]))}static hdlr(e){let t=null;return t="audio"===e.type?ct.constants.HDLR_AUDIO:ct.constants.HDLR_VIDEO,ct.box(ct.types.hdlr,t)}static minf(e){let t=null;return t="audio"===e.type?ct.box(ct.types.smhd,ct.constants.SMHD):ct.box(ct.types.vmhd,ct.constants.VMHD),ct.box(ct.types.minf,t,ct.dinf(),ct.stbl(e))}static dinf(){return ct.box(ct.types.dinf,ct.box(ct.types.dref,ct.constants.DREF))}static stbl(e){return ct.box(ct.types.stbl,ct.stsd(e),ct.box(ct.types.stts,ct.constants.STTS),ct.box(ct.types.stsc,ct.constants.STSC),ct.box(ct.types.stsz,ct.constants.STSZ),ct.box(ct.types.stco,ct.constants.STCO))}static stsd(e){return"audio"===e.type?ct.box(ct.types.stsd,ct.constants.STSD_PREFIX,ct.mp4a(e)):"avc"===e.videoType?ct.box(ct.types.stsd,ct.constants.STSD_PREFIX,ct.avc1(e)):ct.box(ct.types.stsd,ct.constants.STSD_PREFIX,ct.hvc1(e))}static mp4a(e){let t=e.channelCount,i=e.audioSampleRate,o=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t,0,16,0,0,0,0,i>>>8&255,255&i,0,0]);return ct.box(ct.types.mp4a,o,ct.esds(e))}static esds(e){let t=e.config||[],i=t.length,o=new Uint8Array([0,0,0,0,3,23+i,0,1,0,4,15+i,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([i]).concat(t).concat([6,1,2]));return ct.box(ct.types.esds,o)}static avc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return ct.box(ct.types.avc1,r,ct.box(ct.types.avcC,t))}static hvc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return ct.box(ct.types.hvc1,r,ct.box(ct.types.hvcC,t))}static mvex(e){return ct.box(ct.types.mvex,ct.trex(e))}static trex(e){let t=e.id,i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return ct.box(ct.types.trex,i)}static moof(e,t){return ct.box(ct.types.moof,ct.mfhd(e.sequenceNumber),ct.traf(e,t))}static mfhd(e){let t=new Uint8Array([0,0,0,0,e>>>24&255,e>>>16&255,e>>>8&255,255&e]);return ct.box(ct.types.mfhd,t)}static traf(e,t){let i=e.id,o=ct.box(ct.types.tfhd,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),r=ct.box(ct.types.tfdt,new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t])),s=ct.sdtp(e),a=ct.trun(e,s.byteLength+16+16+8+16+8+8);return ct.box(ct.types.traf,o,r,a,s)}static sdtp(e){let t=new Uint8Array(5),i=e.flags;return t[4]=i.isLeading<<6|i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy,ct.box(ct.types.sdtp,t)}static trun(e,t){let i=new Uint8Array(28);t+=36,i.set([0,0,15,1,0,0,0,1,t>>>24&255,t>>>16&255,t>>>8&255,255&t],0);let o=e.duration,r=e.size,s=e.flags,a=e.cts;return i.set([o>>>24&255,o>>>16&255,o>>>8&255,255&o,r>>>24&255,r>>>16&255,r>>>8&255,255&r,s.isLeading<<2|s.dependsOn,s.isDependedOn<<6|s.hasRedundancy<<4|s.isNonSync,0,0,a>>>24&255,a>>>16&255,a>>>8&255,255&a],12),ct.box(ct.types.trun,i)}static mdat(e){return ct.box(ct.types.mdat,e)}}ct.init();class lt extends De{constructor(e){super(),this.player=e,this.isAvc=!0,this.mediaSource=new window.MediaSource,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.cacheTrack={},this.timeInit=!1,this.sequenceNumber=0,this.mediaSourceOpen=!1,this.dropping=!1,this.firstRenderTime=null,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.player.video.$videoElement.src=window.URL.createObjectURL(this.mediaSource);const{debug:t,events:{proxy:i}}=e;i(this.mediaSource,"sourceopen",(()=>{this.mediaSourceOpen=!0,this.player.emit(x.mseSourceOpen)})),i(this.mediaSource,"sourceclose",(()=>{this.player.emit(x.mseSourceClose)})),e.debug.log("MediaSource","init")}destroy(){this.stop(),this.mediaSource=null,this.mediaSourceOpen=!1,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.off(),this.player.debug.log("MediaSource","destroy")}get state(){return this.mediaSource&&this.mediaSource.readyState}get isStateOpen(){return this.state===_}get isStateClosed(){return this.state===$}get isStateEnded(){return this.state===K}get duration(){return this.mediaSource&&this.mediaSource.duration}set duration(e){this.mediaSource.duration=e}decodeVideo(e,t,i,o){const r=this.player;if(r)if(this.hasInit){if(i&&0===e[1]){let t=ot(e.slice(5));const i=this.player.video.videoInfo;i&&i.width&&i.height&&t&&t.codecWidth&&t.codecHeight&&(t.codecWidth!==i.width||t.codecHeight!==i.height)&&(this.player.debug.warn("MediaSource",`width or height is update, width ${i.width}-> ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),this.isInitInfo=!1,this.player.video.init=!1)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){null===this.firstRenderTime&&(this.firstRenderTime=t);const r=t-this.firstRenderTime;this._decodeVideo(e,r,i,o)}else this.player.debug.warn("MediaSource","decodeVideo isDecodeFirstIIframe false")}else if(i&&0===e[1]){const o=15&e[0];if(r.video.updateVideoInfo({encTypeCode:o}),o===Q)return void this.emit(j.mediaSourceH265NotSupport);r._times.decodeStart||(r._times.decodeStart=be()),this._decodeConfigurationRecord(e,t,i,o),this.hasInit=!0}}_decodeConfigurationRecord(e,t,i,o){let r=e.slice(5),s={};s=ot(r);const a={id:1,type:"video",timescale:1e3,duration:0,avcc:r,codecWidth:s.codecWidth,codecHeight:s.codecHeight,videoType:s.videoType},n=ct.generateInitSegment(a);this.isAvc=!0,this.appendBuffer(n.buffer),this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1}_decodeVideo(e,t,i,o){const r=this.player;let s=e.slice(5),a=s.byteLength;const n=r.video.$videoElement,A=r._opt.videoBufferDelay;if(n.buffered.length>1&&(this.removeBuffer(n.buffered.start(0),n.buffered.end(0)),this.timeInit=!1),this.dropping&&t-this.cacheTrack.dts>A)this.dropping=!1,this.cacheTrack={};else if(this.cacheTrack&&t>=this.cacheTrack.dts){let e=8+this.cacheTrack.size,i=new Uint8Array(e);i[0]=e>>>24&255,i[1]=e>>>16&255,i[2]=e>>>8&255,i[3]=255&e,i.set(ct.types.mdat,4),i.set(this.cacheTrack.data,8),this.cacheTrack.duration=t-this.cacheTrack.dts;let o=ct.moof(this.cacheTrack,this.cacheTrack.dts),s=new Uint8Array(o.byteLength+i.byteLength);s.set(o,0),s.set(i,o.byteLength),this.appendBuffer(s.buffer),r.handleRender(),r.updateStats({fps:!0,ts:t,buf:r.demux&&r.demux.delay||0}),r._times.videoStart||(r._times.videoStart=be(),r.handlePlayToRenderTimes())}else r.debug.log("MediaSource","timeInit set false , cacheTrack = {}"),this.timeInit=!1,this.cacheTrack={};this.cacheTrack||(this.cacheTrack={}),this.cacheTrack.id=1,this.cacheTrack.sequenceNumber=++this.sequenceNumber,this.cacheTrack.size=a,this.cacheTrack.dts=t,this.cacheTrack.cts=o,this.cacheTrack.isKeyframe=i,this.cacheTrack.data=s,this.cacheTrack.flags={isLeading:0,dependsOn:i?2:1,isDependedOn:i?1:0,hasRedundancy:0,isNonSync:i?0:1},this.timeInit||1!==n.buffered.length||(r.debug.log("MediaSource","timeInit set true"),this.timeInit=!0,n.currentTime=n.buffered.end(0)),!this.isInitInfo&&n.videoWidth>0&&n.videoHeight>0&&(r.debug.log("MediaSource",`updateVideoInfo: ${n.videoWidth},${n.videoHeight}`),r.video.updateVideoInfo({width:n.videoWidth,height:n.videoHeight}),r.video.initCanvasViewSize(),this.isInitInfo=!0)}appendBuffer(e){const{debug:t,events:{proxy:i}}=this.player;if(null===this.sourceBuffer&&(this.sourceBuffer=this.mediaSource.addSourceBuffer(Z),i(this.sourceBuffer,"error",(e=>{this.player.emit(x.mseSourceBufferError,e)}))),this.mediaSourceAppendBufferError)t.error("MediaSource","this.mediaSourceAppendBufferError is true");else if(this.mediaSourceAppendBufferFull)t.error("MediaSource","this.mediaSourceAppendBufferFull is true");else if(!1===this.sourceBuffer.updating&&this.isStateOpen)try{this.sourceBuffer.appendBuffer(e)}catch(e){t.warn("MediaSource","this.sourceBuffer.appendBuffer()",e.code,e),22===e.code?(this.stop(),this.mediaSourceAppendBufferFull=!0,this.emit(j.mediaSourceFull)):11===e.code?(this.stop(),this.mediaSourceAppendBufferError=!0,this.emit(j.mediaSourceAppendBufferError)):(t.error("MediaSource","appendBuffer error",e),this.player.emit(x.mseSourceBufferError,e))}else this.isStateClosed?this.player.emitError(j.mseSourceBufferError,"mediaSource is not attached to video or mediaSource is closed"):this.isStateEnded?this.player.emitError(j.mseSourceBufferError,"mediaSource is closed"):!0===this.sourceBuffer.updating&&this.player.emit(x.mseSourceBufferBusy)}stop(){this.abortSourceBuffer(),this.removeSourceBuffer(),this.endOfStream()}dropSourceBuffer(e){const t=this.player.video.$videoElement;this.dropping=e,t.buffered.length>0&&t.buffered.end(0)-t.currentTime>1&&(this.player.debug.warn("MediaSource","dropSourceBuffer",`$video.buffered.end(0) is ${t.buffered.end(0)} - $video.currentTime ${t.currentTime}`),t.currentTime=t.buffered.end(0))}removeBuffer(e,t){if(this.isStateOpen&&!1===this.sourceBuffer.updating)try{this.sourceBuffer.remove(e,t)}catch(e){this.player.debug.warn("MediaSource","removeBuffer() error",e)}else this.player.debug.warn("MediaSource","removeBuffer() this.isStateOpen is",this.isStateOpen,"this.sourceBuffer.updating",this.sourceBuffer.updating)}endOfStream(){const e=this.player.video&&this.player.video.$videoElement;if(this.isStateOpen&&e&&e.readyState>=1)try{this.mediaSource.endOfStream()}catch(e){this.player.debug.warn("MediaSource","endOfStream() error",e)}}abortSourceBuffer(){this.isStateOpen&&this.sourceBuffer&&(this.sourceBuffer.abort(),this.sourceBuffer=null)}removeSourceBuffer(){if(!this.isStateClosed&&this.mediaSource&&this.sourceBuffer)try{this.mediaSource.removeSourceBuffer(this.sourceBuffer)}catch(e){this.player.debug.warn("MediaSource","removeSourceBuffer() error",e)}}getSourceBufferUpdating(){return this.sourceBuffer&&this.sourceBuffer.updating}}const ut=()=>"undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,ht=()=>"wakeLock"in navigator;class pt{constructor(e){if(this.player=e,this.enabled=!1,ht()){this._wakeLock=null;const e=()=>{null!==this._wakeLock&&"visible"===document.visibilityState&&this.enable()};document.addEventListener("visibilitychange",e),document.addEventListener("fullscreenchange",e)}else ut()?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("title","No Sleep"),this.noSleepVideo.setAttribute("playsinline",""),this._addSourceToVideo(this.noSleepVideo,"webm","data:video/webm;base64,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"),this._addSourceToVideo(this.noSleepVideo,"mp4","data:video/mp4;base64,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"),this.noSleepVideo.addEventListener("loadedmetadata",(()=>{this.noSleepVideo.duration<=1?this.noSleepVideo.setAttribute("loop",""):this.noSleepVideo.addEventListener("timeupdate",(()=>{this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())}))})))}_addSourceToVideo(e,t,i){var o=document.createElement("source");o.src=i,o.type=`video/${t}`,e.appendChild(o)}get isEnabled(){return this.enabled}enable(){const e=this.player.debug;if(ht())return navigator.wakeLock.request("screen").then((t=>{this._wakeLock=t,this.enabled=!0,e.log("wakeLock","Wake Lock active."),this._wakeLock.addEventListener("release",(()=>{e.log("wakeLock","Wake Lock released.")}))})).catch((t=>{throw this.enabled=!1,e.error("wakeLock",`${t.name}, ${t.message}`),t}));if(ut())return this.disable(),this.noSleepTimer=window.setInterval((()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))}),15e3),this.enabled=!0,Promise.resolve();return this.noSleepVideo.play().then((e=>(this.enabled=!0,e))).catch((e=>{throw this.enabled=!1,e}))}disable(){const e=this.player.debug;ht()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):ut()?this.noSleepTimer&&(e.warn("wakeLock","NoSleep now disabled for older iOS devices."),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}class mt extends De{constructor(e,t){var i;super(),this.$container=e,this._opt=Object.assign({},l,t),this.debug=new he(this),this._opt.useWCS&&(this._opt.useWCS="VideoEncoder"in window),this._opt.useMSE&&(this._opt.useMSE=window.MediaSource&&window.MediaSource.isTypeSupported(Z)),this._opt.wcsUseVideoRender&&(this._opt.wcsUseVideoRender=window.MediaStreamTrackGenerator&&"function"==typeof window.MediaStreamTrackGenerator),this._opt.useMSE&&(this._opt.useWCS&&this.debug.log("Player","useWCS set true->false"),this._opt.forceNoOffscreen||this.debug.log("Player","forceNoOffscreen set false->true"),this._opt.useWCS=!1,this._opt.forceNoOffscreen=!0),this._opt.forceNoOffscreen||("undefined"==typeof OffscreenCanvas?(this._opt.forceNoOffscreen=!0,this._opt.useOffscreen=!1):this._opt.useOffscreen=!0),this._opt.hasAudio||(this._opt.operateBtns.audio=!1),this._opt.hasControl=this._hasControl(),this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._checkHeartTimeout=null,this._checkLoadingTimeout=null,this._checkStatsInterval=null,this._startBpsTime=null,this._isPlayingBeforePageHidden=!1,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0},this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this._videoTimestamp=0,this._audioTimestamp=0,i=this,Object.defineProperty(i,"rect",{get:()=>{const e=i.$container.getBoundingClientRect();return e.width=Math.max(e.width,i.$container.clientWidth),e.height=Math.max(e.height,i.$container.clientHeight),e}}),["bottom","height","left","right","top","width"].forEach((e=>{Object.defineProperty(i,e,{get:()=>i.rect[e]})})),this.events=new pe(this),this.video=new Je(this),this._opt.hasAudio&&(this.audio=new Ge(this)),this.recorder=new qe(this),this._onlyMseOrWcsVideo()?this.loaded=!0:this.decoderWorker=new Ze(this),this.stream=null,this.demux=null,this._lastVolume=null,this._opt.useWCS&&(this.webcodecsDecoder=new rt(this),this.loaded=!0),this._opt.useMSE&&(this.mseDecoder=new lt(this),this.loaded=!0),this.control=new dt(this),Be()&&(this.keepScreenOn=new pt(this)),(e=>{try{const t=t=>{Te(t)===e.$container&&(e.emit(D.fullscreen,e.fullscreen),e.fullscreen?e._opt.useMSE&&e.resize():e.resize())};me.on("change",t),e.events.destroys.push((()=>{me.off("change",t)}))}catch(e){}if(e.on(x.decoderWorkerInit,(()=>{e.debug.log("player","has loaded"),e.loaded=!0})),e.on(x.play,(()=>{e.loading=!1})),e.on(x.fullscreen,(t=>{if(t)try{me.request(e.$container).then((()=>{})).catch((t=>{Be()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)}))}catch(t){Be()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)}else try{me.exit().then((()=>{e.webFullscreen&&(e.webFullscreen=!1)})).catch((()=>{e.webFullscreen=!1}))}catch(t){e.webFullscreen=!1}})),Be()&&e.on(x.webFullscreen,(t=>{t?e.$container.classList.add("jessibuca-fullscreen-web"):e.$container.classList.remove("jessibuca-fullscreen-web"),e.emit(D.fullscreen,e.fullscreen)})),e.on(x.resize,(()=>{e.video&&e.video.resize()})),e._opt.debug){const t=[x.timeUpdate];Object.keys(x).forEach((i=>{e.on(x[i],(o=>{t.includes(i)||e.debug.log("player events",x[i],o)}))})),Object.keys(j).forEach((t=>{e.on(j[t],(i=>{e.debug.log("player event error",j[t],i)}))}))}})(this),(e=>{const{_opt:t,debug:i,events:{proxy:o}}=e;t.supportDblclickFullscreen&&o(e.$container,"dblclick",(t=>{const i=Te(t).nodeName.toLowerCase();"canvas"!==i&&"video"!==i||(e.fullscreen=!e.fullscreen)})),o(document,"visibilitychange",(()=>{t.hiddenAutoPause&&(i.log("visibilitychange",document.visibilityState,e._isPlayingBeforePageHidden),"visible"===document.visibilityState?e._isPlayingBeforePageHidden&&e.play():(e._isPlayingBeforePageHidden=e.playing,e.playing&&e.pause()))})),o(window,"fullscreenchange",(()=>{null!==e.keepScreenOn&&"visible"===document.visibilityState&&e.enableWakeLock()}))})(this),this._opt.useWCS&&this.debug.log("Player","use WCS"),this._opt.useMSE&&this.debug.log("Player","use MSE"),this._opt.useOffscreen&&this.debug.log("Player","use offscreen"),this.debug.log("Player options",this._opt)}destroy(){this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._lastVolume=null,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.video&&(this.video.destroy(),this.video=null),this.audio&&(this.audio.destroy(),this.audio=null),this.stream&&(this.stream.destroy(),this.stream=null),this.recorder&&(this.recorder.destroy(),this.recorder=null),this.control&&(this.control.destroy(),this.control=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.demux&&(this.demux.destroy(),this.demux=null),this.events&&(this.events.destroy(),this.events=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.releaseWakeLock(),this.keepScreenOn=null,this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this.emit("destroy"),this.off(),this.debug.log("play","destroy end")}set fullscreen(e){Be()&&this._opt.useWebFullScreen?(this.emit(x.webFullscreen,e),setTimeout((()=>{this.updateOption({rotate:e?270:0}),this.resize()}),10)):this.emit(x.fullscreen,e)}get fullscreen(){return me.isFullscreen||this.webFullscreen}set webFullscreen(e){this.emit(x.webFullscreen,e)}get webFullscreen(){return this.$container.classList.contains("jessibuca-fullscreen-web")}set loaded(e){this._hasLoaded=e}get loaded(){return this._hasLoaded}set playing(e){e&&(this.loading=!1),this.playing!==e&&(this._playing=e,this.emit(x.playing,e),this.emit(x.volumechange,this.volume),e?this.emit(x.play):this.emit(x.pause))}get playing(){return this._playing}get volume(){return this.audio&&this.audio.volume||0}set volume(e){e!==this.volume&&(this.audio&&this.audio.setVolume(e),this._lastVolume=e)}get lastVolume(){return this._lastVolume}set loading(e){this.loading!==e&&(this._loading=e,this.emit(x.loading,this._loading))}get loading(){return this._loading}set recording(e){e?this.playing&&this.recorder&&this.recorder.startRecord():this.recorder&&this.recorder.stopRecordAndSave()}get recording(){return!!this.recorder&&this.recorder.recording}set audioTimestamp(e){null!==e&&(this._audioTimestamp=e)}get audioTimestamp(){return this._audioTimestamp}set videoTimestamp(e){null!==e&&(this._videoTimestamp=e,this._opt.useWCS||this._opt.useMSE||this.audioTimestamp&&this.videoTimestamp&&this.audio&&this.audio.emit(x.videoSyncAudio,{audioTimestamp:this.audioTimestamp,videoTimestamp:this.videoTimestamp,diff:this.audioTimestamp-this.videoTimestamp}))}get videoTimestamp(){return this._videoTimestamp}get isDebug(){return!0===this._opt.debug}updateOption(e){this._opt=Object.assign({},this._opt,e)}init(){return new Promise(((e,t)=>{this.stream||(this.stream=new ze(this)),this.audio||this._opt.hasAudio&&(this.audio=new Ge(this)),this.demux||(this.demux=new et(this)),this._opt.useWCS&&(this.webcodecsDecoder||(this.webcodecsDecoder=new rt(this))),this._opt.useMSE&&(this.mseDecoder||(this.mseDecoder=new lt(this))),this.decoderWorker||this._onlyMseOrWcsVideo()?e():(this.decoderWorker=new Ze(this),this.once(x.decoderWorkerInit,(()=>{e()})))}))}play(e,t){return new Promise(((i,o)=>{if(!e&&!this._opt.url)return o();this.loading=!0,this.playing=!1,this._times.playInitStart=be(),e||(e=this._opt.url),this._opt.url=e,this.clearCheckHeartTimeout(),this.init().then((()=>{this._times.playStart=be(),this._opt.isNotMute&&this.mute(!1),this.webcodecsDecoder&&this.webcodecsDecoder.once(j.webcodecsH265NotSupport,(()=>{this.emit(j.webcodecsH265NotSupport),this._opt.autoWasm||this.emit(x.error,j.webcodecsH265NotSupport)})),this.mseDecoder&&(this.mseDecoder.once(j.mediaSourceH265NotSupport,(()=>{this.emit(j.mediaSourceH265NotSupport),this._opt.autoWasm||this.emit(x.error,j.mediaSourceH265NotSupport)})),this.mseDecoder.once(j.mediaSourceFull,(()=>{this.emitError(j.mediaSourceFull)})),this.mseDecoder.once(j.mediaSourceAppendBufferError,(()=>{this.emitError(j.mediaSourceAppendBufferError)})),this.mseDecoder.once(j.mediaSourceBufferListLarge,(()=>{this.emitError(j.mediaSourceBufferListLarge)})),this.mseDecoder.once(j.mediaSourceAppendBufferEndTimeout,(()=>{this.emitError(j.mediaSourceAppendBufferEndTimeout)}))),this.enableWakeLock(),this.stream.fetchStream(e,t),this.checkLoadingTimeout(),this.stream.once(j.fetchError,(e=>{o(e)})),this.stream.once(j.websocketError,(e=>{o(e)})),this.stream.once(x.streamEnd,(()=>{o()})),this.stream.once(x.streamSuccess,(()=>{i(),this._times.streamResponse=be(),this.video.play(),this.checkStatsInterval()}))})).catch((e=>{o(e)}))}))}close(){return new Promise(((e,t)=>{this._close().then((()=>{this.video&&this.video.clearView(),e()}))}))}resumeAudioAfterPause(){this.lastVolume&&(this.volume=this.lastVolume)}_close(){return new Promise(((e,t)=>{this.stream&&(this.stream.destroy(),this.stream=null),this.demux&&(this.demux.destroy(),this.demux=null),this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.audio&&(this.audio.destroy(),this.audio=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.playing=!1,this.loading=!1,this.recording=!1,this.video&&(this.video.resetInit(),this.video.pause(!0)),this.releaseWakeLock(),this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},setTimeout((()=>{e()}),0)}))}pause(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]?this.close():this._close()}mute(e){this.audio&&this.audio.mute(e)}resize(){this.video.resize()}startRecord(e,t){this.recording||(this.recorder.setFileName(e,t),this.recording=!0)}stopRecordAndSave(){this.recording&&(this.recording=!1)}_hasControl(){let e=!1,t=!1;return Object.keys(this._opt.operateBtns).forEach((e=>{this._opt.operateBtns[e]&&(t=!0)})),(this._opt.showBandwidth||this._opt.text||t)&&(e=!0),e}_onlyMseOrWcsVideo(){return!1===this._opt.hasAudio&&(this._opt.useMSE||this._opt.useWCS&&!this._opt.useOffscreen)}checkHeart(){this.clearCheckHeartTimeout(),this.checkHeartTimeout()}checkHeartTimeout(){this._checkHeartTimeout=setTimeout((()=>{if(this.playing){if(0!==this._stats.fps)return;this.pause().then((()=>{this.emit(x.timeout,x.delayTimeout),this.emit(x.delayTimeout)}))}}),1e3*this._opt.heartTimeout)}checkStatsInterval(){this._checkStatsInterval=setInterval((()=>{this.updateStats()}),1e3)}clearCheckHeartTimeout(){this._checkHeartTimeout&&(clearTimeout(this._checkHeartTimeout),this._checkHeartTimeout=null)}checkLoadingTimeout(){this._checkLoadingTimeout=setTimeout((()=>{this.playing||this.pause().then((()=>{this.emit(x.timeout,x.loadingTimeout),this.emit(x.loadingTimeout)}))}),1e3*this._opt.loadingTimeout)}clearCheckLoadingTimeout(){this._checkLoadingTimeout&&(clearTimeout(this._checkLoadingTimeout),this._checkLoadingTimeout=null)}clearStatsInterval(){this._checkStatsInterval&&(clearInterval(this._checkStatsInterval),this._checkStatsInterval=null)}handleRender(){this.loading&&(this.emit(x.start),this.loading=!1,this.clearCheckLoadingTimeout()),this.playing||(this.playing=!0),this.checkHeart()}updateStats(e){e=e||{},this._startBpsTime||(this._startBpsTime=be()),ke(e.ts)&&(this._stats.ts=e.ts),ke(e.buf)&&(this._stats.buf=e.buf),e.fps&&(this._stats.fps+=1),e.abps&&(this._stats.abps+=e.abps),e.vbps&&(this._stats.vbps+=e.vbps);const t=be();t-this._startBpsTime<1e3||(this.emit(x.stats,this._stats),this.emit(x.performance,function(e){let t=0;return e>=24?t=2:e>=15&&(t=1),t}(this._stats.fps)),this._stats.fps=0,this._stats.abps=0,this._stats.vbps=0,this._startBpsTime=t)}resetStats(){this._startBpsTime=null,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0}}enableWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.enable()}releaseWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.disable()}handlePlayToRenderTimes(){const e=this._times;e.playTimestamp=e.playStart-e.playInitStart,e.streamTimestamp=e.streamStart-e.playStart,e.streamResponseTimestamp=e.streamResponse-e.streamStart,e.demuxTimestamp=e.demuxStart-e.streamResponse,e.decodeTimestamp=e.decodeStart-e.demuxStart,e.videoTimestamp=e.videoStart-e.decodeStart,e.allTimestamp=e.videoStart-e.playInitStart,this.emit(x.playToRenderTimes,e)}getOption(){return this._opt}emitError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";this.emit(x.error,e,t),this.emit(e,t)}}class gt extends De{constructor(e){super();let t=e,i=e.container;if("string"==typeof e.container&&(i=document.querySelector(e.container)),!i)throw new Error("Jessibuca need container option");if("CANVAS"===i.nodeName||"VIDEO"===i.nodeName)throw new Error(`Jessibuca container type can not be ${i.nodeName} type`);if(t.videoBuffer>=t.heartTimeout)throw new Error(`Jessibuca videoBuffer ${t.videoBuffer}s must be less than heartTimeout ${t.heartTimeout}s`);i.classList.add("jessibuca-container"),delete t.container,t.forceNoOffscreen=!0,Be()&&(t.controlAutoHide=!1),ke(t.videoBuffer)&&(t.videoBuffer=1e3*Number(t.videoBuffer)),ke(t.timeout)&&(Re(t.loadingTimeout)&&(t.loadingTimeout=t.timeout),Re(t.heartTimeout)&&(t.heartTimeout=t.timeout)),this._opt=t,this.$container=i,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.events=new pe(this),this.debug=new he(this),this._initPlayer(i,t)}destroy(){this.events&&(this.events.destroy(),this.events=null),this.player&&(this.player.destroy(),this.player=null),this.$container=null,this._opt=null,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.off()}_initPlayer(e,t){this.player=new mt(e,t),this.debug.log("jessibuca","_initPlayer",this.player.getOption()),this._bindEvents()}_resetPlayer(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.player.destroy(),this.player=null,this._opt=Object.assign(this._opt,e),this._opt.url="",this._initPlayer(this.$container,this._opt)}_bindEvents(){Object.keys(D).forEach((e=>{this.player.on(D[e],(t=>{this.emit(e,t)}))}))}setDebug(e){this.player.updateOption({debug:!!e})}mute(){this.player.mute(!0)}cancelMute(){this.player.mute(!1)}setVolume(e){this.player.volume=e}audioResume(){this.player.audio&&this.player.audio.audioEnabled(!0)}setTimeout(e){e=Number(e),this.player.updateOption({timeout:e,loadingTimeout:e,heartTimeout:e})}setScaleMode(e){let t={isFullResize:!1,isResize:!1};switch(e=Number(e)){case P:t.isFullResize=!1,t.isResize=!1;break;case G:t.isFullResize=!1,t.isResize=!0;break;case N:t.isFullResize=!0,t.isResize=!0}this.player.updateOption(t),this.resize()}pause(){return new Promise(((e,t)=>{this.player?this.player.pause().then((()=>{e()})).catch((e=>{t(e)})):t("player is null")}))}close(){return this._opt.url="",this._opt.playOptions={},this.player.close()}clearView(){this.player.video.clearView()}play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{if(!e&&!this._opt.url)return this.emit(x.error,j.playError),void o("play url is empty");e?this._opt.url?e===this._opt.url?this.player.playing?i():(this.clearView(),this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((e=>{this.debug.warn("jessibuca","pause -> play and play error",e),this.player.pause().then((()=>{o(e)}))}))):this.player.pause().then((()=>{this.clearView(),this._play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("jessibuca","this._play error",e),o(e)}))})).catch((e=>{this.debug.warn("jessibuca","this._opt.url is null and pause error",e),o(e)})):this._play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("jessibuca","this._play error",e),o(e)})):this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((e=>{this.debug.warn("jessibuca","url is null and play error",e),this.player.pause().then((()=>{o(e)}))}))}))}_play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{this._opt.url=e,this._opt.playOptions=t;const r=0===e.indexOf("http"),d=r?a:s,c=r||-1!==e.indexOf(".flv")||this._opt.isFlv?n:A;this.player.updateOption({protocol:d,demuxType:c}),this.player.once(j.webglAlignmentError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webglAlignmentError"),this._resetPlayer({openWebglAlignment:!0}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webglAlignmentError and play success")})).catch((()=>{this.debug.log("Jessibuca","webglAlignmentError and play error")}))}))})),this.player.once(j.mediaSourceH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")})).catch((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play error")})))}))})),this.player.once(j.mediaSourceFull,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source full"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source full and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source full and reset player and play error")}))}))})),this.player.once(j.mediaSourceAppendBufferError,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source append buffer error"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source append buffer error and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source append buffer error and reset player and play error")}))}))})),this.player.once(j.mediaSourceBufferListLarge,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source buffer list large"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source buffer list large and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source buffer list large and reset player and play error")}))}))})),this.player.once(j.mediaSourceAppendBufferEndTimeout,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","media source append buffer end timeout"),this._resetPlayer(),this.play(e,t).then((()=>{this.debug.log("Jessibuca","media source append buffer end timeout and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","media source append buffer end timeout and reset player and play error")}))}))})),this.player.once(j.mseSourceBufferError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","auto wasm [mse-> wasm] reset player and play error")})))}))})),this.player.once(j.webcodecsH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","auto wasm [wcs-> wasm] reset player and play error")})))}))})),this.player.once(j.webcodecsWidthOrHeightChange,(()=>{this.pause().then((()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play"),this._resetPlayer({useWCS:!0}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","webcodecs Width Or Height Change reset player and play error")}))}))})),this.player.once(j.webcodecsDecodeError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.debug.log("Jessibuca","webcodecs decode error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","webcodecs decode error reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","webcodecs decode error reset player and play error")})))}))})),this.player.once(j.wasmDecodeError,(()=>{this.player._opt.wasmDecodeErrorReplay&&this.pause().then((()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play success")})).catch((()=>{this.debug.warn("Jessibuca","wasm decode error and reset player and play error")}))}))})),this.player.on(x.delayTimeout,(()=>{this.player._opt.heartTimeoutReplay&&(this._heartTimeoutReplayTimes{this._heartTimeoutReplayTimes=0})).catch((()=>{})))})),this.player.on(x.loadingTimeout,(()=>{this.player._opt.loadingTimeoutReplay&&(this._loadingTimeoutReplayTimes{this._loadingTimeoutReplayTimes=0})).catch((()=>{})))})),this.hasLoaded()?this.player.play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("Jessibuca","hasLoaded and play error",e),this.player&&this.player.pause().then((()=>{o(e)}))})):this.player.once(x.decoderWorkerInit,(()=>{this.player.play(e,t).then((()=>{i()})).catch((e=>{this.debug.warn("Jessibuca","decoderWorkerInit and play error",e),this.player&&this.player.pause().then((()=>{o(e)}))}))}))}))}resize(){this.player.resize()}setBufferTime(e){e=Number(e),this.player.updateOption({videoBuffer:1e3*e}),this.player.decoderWorker&&this.player.decoderWorker.updateWorkConfig({key:"videoBuffer",value:1e3*e})}setRotate(e){e=parseInt(e,10);this._opt.rotate!==e&&-1!==[0,90,180,270].indexOf(e)&&(this.player.updateOption({rotate:e}),this.resize())}hasLoaded(){return this.player.loaded}setKeepScreenOn(){this.player.updateOption({keepScreenOn:!0})}setFullscreen(e){const t=!!e;this.player.fullscreen!==t&&(this.player.fullscreen=t)}screenshot(e,t,i,o){return this.player.video?this.player.video.screenshot(e,t,i,o):""}startRecord(e,t){return new Promise(((i,o)=>{this.player.playing?(this.player.startRecord(e,t),i()):o()}))}stopRecordAndSave(){this.player.recording&&this.player.stopRecordAndSave()}isPlaying(){return!!this.player&&this.player.playing}isMute(){return!this.player.audio||this.player.audio.isMute}isRecording(){return this.player.recorder.recording}}return r(gt,"ERROR",j),r(gt,"TIMEOUT",{loadingTimeout:x.loadingTimeout,delayTimeout:x.delayTimeout}),window.Jessibuca=gt,gt})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).jessibuca=t()}(this,function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function i(e,t){return e(t={exports:{}},t.exports),t.exports}var o=i(function(e){function t(i){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(i)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports});t(o);var r=i(function(e){var t=o.default;e.exports=function(e,i){if("object"!=t(e)||!e)return e;var o=e[Symbol.toPrimitive];if(void 0!==o){var r=o.call(e,i||"default");if("object"!=t(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===i?String:Number)(e)},e.exports.__esModule=!0,e.exports.default=e.exports});t(r);var s=i(function(e){var t=o.default;e.exports=function(e){var i=r(e,"string");return"symbol"==t(i)?i:i+""},e.exports.__esModule=!0,e.exports.default=e.exports});t(s);var a=t(i(function(e){e.exports=function(e,t,i){return(t=s(t))in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e},e.exports.__esModule=!0,e.exports.default=e.exports}));const n=0,A=1,d="flv",c="m7s",l="mp4",u="webm",h="jessibuca",p='"3.3.21"',g={videoBuffer:1e3,videoBufferDelay:1e3,isResize:!0,isFullResize:!1,isFlv:!1,debug:!1,hotKey:!1,loadingTimeout:10,heartTimeout:5,timeout:10,loadingTimeoutReplay:!0,heartTimeoutReplay:!0,loadingTimeoutReplayTimes:3,heartTimeoutReplayTimes:3,supportDblclickFullscreen:!1,showBandwidth:!1,keepScreenOn:!1,isNotMute:!1,hasAudio:!0,hasVideo:!0,operateBtns:{fullscreen:!1,screenshot:!1,play:!1,audio:!1,record:!1},controlAutoHide:!1,hasControl:!1,loadingText:"",background:"",decoder:"decoder.js",url:"",rotate:0,forceNoOffscreen:!0,hiddenAutoPause:!1,protocol:A,demuxType:d,useWCS:!1,wcsUseVideoRender:!1,useMSE:!1,useOffscreen:!1,autoWasm:!0,wasmDecodeErrorReplay:!0,openWebglAlignment:!1,wasmDecodeAudioSyncVideo:!1,recordType:u,useWebFullScreen:!1,loadingDecoderWorkerTimeout:10,autoUseSystemFullScreen:!0},m="init",f="initVideo",b="render",y="playAudio",v="initAudio",w="audioCode",S="videoCode",E="wasmError",B="Invalid NAL unit size",C=1,R=2,k=8,T=9,I="init",x="decode",D="audioDecode",j="close",L="updateConfig",F={fullscreen:"fullscreen$2",webFullscreen:"webFullscreen",decoderWorkerInit:"decoderWorkerInit",play:"play",playing:"playing",pause:"pause",mute:"mute",load:"load",loading:"loading",videoInfo:"videoInfo",timeUpdate:"timeUpdate",audioInfo:"audioInfo",log:"log",error:"error",kBps:"kBps",timeout:"timeout",delayTimeout:"delayTimeout",loadingTimeout:"loadingTimeout",stats:"stats",performance:"performance",record:"record",recording:"recording",recordingTimestamp:"recordingTimestamp",recordStart:"recordStart",recordEnd:"recordEnd",recordCreateError:"recordCreateError",buffer:"buffer",videoFrame:"videoFrame",start:"start",metadata:"metadata",resize:"resize",streamEnd:"streamEnd",streamSuccess:"streamSuccess",streamMessage:"streamMessage",streamError:"streamError",volumechange:"volumechange",volume:"volume",destroy:"destroy",mseSourceOpen:"mseSourceOpen",mseSourceClose:"mseSourceClose",mseSourceBufferError:"mseSourceBufferError",mseSourceBufferBusy:"mseSourceBufferBusy",mseSourceBufferFull:"mseSourceBufferFull",videoWaiting:"videoWaiting",videoTimeUpdate:"videoTimeUpdate",videoSyncAudio:"videoSyncAudio",playToRenderTimes:"playToRenderTimes"},M={load:F.load,timeUpdate:F.timeUpdate,videoInfo:F.videoInfo,audioInfo:F.audioInfo,error:F.error,kBps:F.kBps,log:F.log,start:F.start,timeout:F.timeout,loadingTimeout:F.loadingTimeout,delayTimeout:F.delayTimeout,fullscreen:"fullscreen",webFullscreen:F.webFullscreen,play:F.play,pause:F.pause,mute:F.mute,stats:F.stats,volumechange:F.volumechange,performance:F.performance,recordingTimestamp:F.recordingTimestamp,recordStart:F.recordStart,recordEnd:F.recordEnd,playToRenderTimes:F.playToRenderTimes,volume:F.volume},O={playError:"playIsNotPauseOrUrlIsNull",fetchError:"fetchError",websocketError:"websocketError",webcodecsH265NotSupport:"webcodecsH265NotSupport",webcodecsConfigureError:"webcodecsConfigureError",webcodecsDecodeError:"webcodecsDecodeError",webcodecsWidthOrHeightChange:"webcodecsWidthOrHeightChange",mediaSourceH265NotSupport:"mediaSourceH265NotSupport",mediaSourceFull:F.mseSourceBufferFull,mseSourceBufferError:F.mseSourceBufferError,mediaSourceAppendBufferError:"mediaSourceAppendBufferError",mediaSourceBufferListLarge:"mediaSourceBufferListLarge",mediaSourceAppendBufferEndTimeout:"mediaSourceAppendBufferEndTimeout",wasmDecodeError:"wasmDecodeError",webglAlignmentError:"webglAlignmentError",webglContextLostError:"webglContextLostError",webglInitError:"webglInitError"},V="notConnect",U="open",Q="close",W="error",J={download:"download",base64:"base64",blob:"blob"},G={7:"H264(AVC)",12:"H265(HEVC)"},P=12,N={10:"AAC",7:"ALAW",8:"MULAW"},H=38,z=0,Y=1,X=2,q="webcodecs",Z="webgl",K="offscreen",_="key",$="delta",ee='video/mp4; codecs="avc1.64002A"',te="ended",ie="open",oe="closed",re=1e3,se=27,ae=38,ne=40,Ae="A key frame is required after configure() or flush()",de="Cannot call 'decode' on a closed codec",ce="The user aborted a request",le="AbortError",ue="AbortError",he=0,pe=1,ge=3,me=16;class fe{constructor(e){this.log=(t,...i)=>{e._opt&&e._opt.debug&&console.log(`Jb: [${t}]`,...i)},this.warn=(t,...i)=>{e._opt&&e._opt.debug&&console.warn(`Jb: [${t}]`,...i)},this.error=(e,...t)=>{console.error(`Jb: [${e}]`,...t)}}}class be{constructor(e){this.destroys=[],this.proxy=this.proxy.bind(this),this.master=e}proxy(e,t,i,o={}){if(!e)return;if(Array.isArray(t))return t.map(t=>this.proxy(e,t,i,o));e.addEventListener(t,i,o);const r=()=>e.removeEventListener(t,i,o);return this.destroys.push(r),r}destroy(){this.master.debug&&this.master.debug.log("Events","destroy"),this.destroys.forEach(e=>e())}}var ye=i(function(e){!function(){var t="undefined"!=typeof window&&void 0!==window.document?window.document:{},i=e.exports,o=function(){for(var e,i=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],o=0,r=i.length,s={};o{Be(e,i,t[i])}),e.style[t]=i,e}function Ce(e,t,i=!0){if(!e)return 0;const o=getComputedStyle(e,null).getPropertyValue(t);return i?parseFloat(o):o}function Re(){return performance&&"function"==typeof performance.now?performance.now():Date.now()}function ke(e){let t=0,i=Re();return o=>{t+=o;const r=Re(),s=r-i;s>=1e3&&(e(t/s*1e3),i=r,t=0)}}function Te(){return/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase())}function Ie(e){return null==e}function xe(e){return!0===e||!1===e}function De(e){return!Ie(e)}function je(e){var t;if(e>-1){var i=Math.floor(e/3600),o=Math.floor(e/60)%60,r=e%60;t=i<10?"0"+i+":":i+":",o<10&&(t+="0"),t+=o+":",(r=Math.round(r))<10&&(t+="0"),t+=r.toFixed(0)}return t}function Le(e){const t=e||window.event;return t.target||t.srcElement}function Fe(e){let t=!1;return e&&(e.innerHTML="",e.parentNode&&(e.parentNode.removeChild(e),t=!0)),t}function Me(e,t){let i=[];i[0]=t?28:44,i[1]=1,i[2]=0,i[3]=0,i[4]=0;const o=new Uint8Array(i.length+e.byteLength);return o.set(i,0),o.set(e,i.length),o}function Oe(e){return!0!==e&&"true"!==e}ye.isEnabled,(()=>{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){}})();class Ve{on(e,t,i){const o=this.e||(this.e={});return(o[e]||(o[e]=[])).push({fn:t,ctx:i}),this}once(e,t,i){const o=this;function r(...s){o.off(e,r),t.apply(i,s)}return r._=t,this.on(e,r,i)}emit(e,...t){const i=((this.e||(this.e={}))[e]||[]).slice();for(let e=0;e{delete i[e]}),void delete this.e;const o=i[e],r=[];if(o&&t)for(let e=0,i=o.length;e=200&&t.status<=299}function Ge(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 Pe=Qe.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),Ne="object"!=typeof window||window!==Qe?function(){}:"download"in HTMLAnchorElement.prototype&&!Pe?function(e,t,i){var o=Qe.URL||Qe.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?Je(r.href)?We(e,t,i):Ge(r,r.target="_blank"):Ge(r)):(r.href=o.createObjectURL(e),setTimeout(function(){o.revokeObjectURL(r.href)},4e4),setTimeout(function(){Ge(r)},0))}:"msSaveOrOpenBlob"in navigator?function(e,t,i){if(t=t||e.name||"download","string"==typeof e)if(Je(e))We(e,t,i);else{var o=document.createElement("a");o.href=e,o.target="_blank",setTimeout(function(){Ge(o)})}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,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof e)return We(e,t,i);var r="application/octet-stream"===e.type,s=/constructor/i.test(Qe.HTMLElement)||Qe.safari,a=/CriOS\/[\d]+/.test(navigator.userAgent);if((a||r&&s||Pe)&&"undefined"!=typeof FileReader){var n=new FileReader;n.onloadend=function(){var e=n.result;e=a?e:e.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=e:location=e,o=null},n.readAsDataURL(e)}else{var A=Qe.URL||Qe.webkitURL,d=A.createObjectURL(e);o?o.location=d:location.href=d,o=null,setTimeout(function(){A.revokeObjectURL(d)},4e4)}};class He extends Ue{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.contextGlRender=null,this.contextGlDestroy=null,this.bitmaprenderer=null,this.renderType=null,this.isContextGlRenderLost=!1,this.videoInfo={width:"",height:"",encType:""},this._initCanvasRender(),this.player.debug.log("CanvasVideo","init")}async destroy(){super.destroy(),this.contextGl&&(this.contextGl=null),this.context2D&&(this.context2D=null),this.contextGlRender&&(this.contextGlDestroy&&this.contextGlDestroy(),this.contextGlDestroy=null,this.contextGlRender=null),this.bitmaprenderer&&(this.bitmaprenderer=null),this.renderType=null,this.isContextGlRenderLost=!1,this.player.debug.log("CanvasVideoLoader","destroy")}_initContextGl(){if(this.contextGl=function(e){let t=null;const i=["webgl","experimental-webgl","moz-webgl","webkit-3d"];let o=0;for(;!t&&o{var i=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),o=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");t&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var r=e.createShader(e.VERTEX_SHADER);e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(r));var s=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(s,o),e.compileShader(s),e.getShaderParameter(s,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(s));var a=e.createProgram();e.attachShader(a,r),e.attachShader(a,s),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var A=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(A),e.vertexAttribPointer(A,2,e.FLOAT,!1,0,0);var d=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,d),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function l(t,i){var o=e.createTexture();return e.bindTexture(e.TEXTURE_2D,o),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,t),i),o}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var u=l("ySampler",0),h=l("uSampler",1),p=l("vSampler",2);return{render:function(t,i,o,r,s){e.viewport(0,0,t,i),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,u),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t,i,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,h),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,r),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,s),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(n),e.deleteBuffer(d),e.deleteTexture(u),e.deleteTexture(h),e.deleteTexture(p)}catch(e){}}}})(this.contextGl,this.player._opt.openWebglAlignment);this.contextGlRender=e.render,this.contextGlDestroy=e.destroy}else this.player.debug.error("CanvasVideoLoader","init webgl fail"),this.player.emitError(O.webglInitError)}_initContext2D(){this.context2D=this.$videoElement.getContext("2d")}_initCanvasRender(){this.player._opt.useWCS&&!this._supportOffscreen()?(this.renderType=q,this._initContext2D()):this._supportOffscreen()?(this.renderType=K,this._bindOffscreen()):(this.renderType=Z,this._initContextGl())}_supportOffscreen(){return"function"==typeof this.$videoElement.transferControlToOffscreen&&this.player._opt.useOffscreen}_bindOffscreen(){this.bitmaprenderer=this.$videoElement.getContext("bitmaprenderer")}initCanvasViewSize(){this.$videoElement.width=this.videoInfo.width,this.$videoElement.height=this.videoInfo.height,this.resize()}render(e){switch(this.player.videoTimestamp=e.ts,this.renderType){case K:this.bitmaprenderer.transferFromImageBitmap(e.buffer);break;case Z:if(this.isContextGlRenderLost)return;try{this.contextGlRender(this.$videoElement.width,this.$videoElement.height,e.output[0],e.output[1],e.output[2])}catch(e){this.player.debug.error("CanvasVideoLoader","webgl render error and emit webglContextLostError",e),this.isContextGlRenderLost=!0,this.player.emitError(O.webglContextLostError)}break;case q:this.context2D.drawImage(e.videoFrame,0,0,this.$videoElement.width,this.$videoElement.height),(t=e.videoFrame).close?t.close():t.destroy&&t.destroy()}var t}screenshot(e,t,i,o){e=e||Se(),o=o||J.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&J[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement.toDataURL(r[t]||r.png,s);if(o===J.base64)return a;{const t=we(a);if(o===J.blob)return t;o===J.download&&Ne(t,e)}}clearView(){switch(this.renderType){case K:(function(e,t){const i=document.createElement("canvas");return i.width=e,i.height=t,window.createImageBitmap(i,0,0,e,t)})(this.$videoElement.width,this.$videoElement.height).then(e=>{this.bitmaprenderer.transferFromImageBitmap(e)});break;case Z:this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT);break;case q:this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height)}}resize(){this.player.debug.log("canvasVideo","resize");const e=this.player._opt;let t=this.player.width,i=this.player.height;this.player.isControlBarShow()&&(Te()&&this.player.fullscreen&&e.useWebFullScreen?t-=H:i-=H);let o=this.$videoElement.width,r=this.$videoElement.height;const s=e.rotate;let a=(t-o)/2,n=(i-r)/2;270!==s&&90!==s||(o=this.$videoElement.height,r=this.$videoElement.width);const A=t/o,d=i/r;let c=A>d?d:A;e.isResize||A!==d&&(c=A+","+d),e.isFullResize&&(c=A>d?A:d);let l="scale("+c+")";s&&(l+=" rotate("+s+"deg)"),this.$videoElement.style.transform=l,this.$videoElement.style.left=a+"px",this.$videoElement.style.top=n+"px"}}class ze extends Ue{constructor(e){super(),this.player=e;const t=document.createElement("video"),i=document.createElement("canvas");t.muted=!0,t.disablePictureInPicture=!0,function(){const e=window.navigator.userAgent.toLowerCase();return/android/i.test(e)}()&&(t.poster="noposter"),t.style.position="absolute",t.style.top=0,t.style.left=0,this._delayPlay=!1,e.$container.appendChild(t),this.videoInfo={width:"",height:"",encType:""};const o=this.player._opt;o.useWCS&&o.wcsUseVideoRender&&(this.trackGenerator=new MediaStreamTrackGenerator({kind:"video"}),t.srcObject=new MediaStream([this.trackGenerator]),this.vwriter=this.trackGenerator.writable.getWriter()),this.$videoElement=t,this.$canvasElement=i,this.canvasContext=i.getContext("2d"),this.fixChromeVideoFlashBug(),this.resize();const{proxy:r}=this.player.events;r(this.$videoElement,"canplay",()=>{this.player.debug.log("Video","canplay"),this._delayPlay&&(this.player.debug.log("Video","canplay and _delayPlay is true and next play()"),this._play())}),r(this.$videoElement,"waiting",()=>{this.player.debug.log("Video","waiting")}),r(this.$videoElement,"timeupdate",e=>{const t=parseInt(e.timeStamp,10);this.player.emit(F.timeUpdate,t),!this.isPlaying()&&this.init&&(this.player.debug.log("Video","timeupdate and this.isPlaying is false and retry play"),this.$videoElement.play())}),this.player.debug.log("Video","init")}async destroy(){super.destroy(),this.$canvasElement=null,this.canvasContext=null,this.$videoElement&&(this.$videoElement.pause(),this.$videoElement.currentTime=0,this.$videoElement.src="",this.$videoElement.removeAttribute("src"),this.$videoElement=null),this.trackGenerator&&(this.trackGenerator.stop(),this.trackGenerator=null),this.vwriter&&(await this.vwriter.close(),this.vwriter=null),this.player.debug.log("Video","destroy")}fixChromeVideoFlashBug(){const e=function(){const e=navigator.userAgent.toLowerCase(),t={},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 o in i)if(i[o]){let i="";if("IE"===o)i=e.match(/(msie\s|trident.*rv:)([\w.]+)/)[2];else if("Chrome"===o){for(let e in navigator.mimeTypes)"application/360softmgrplugin"===navigator.mimeTypes[e].type&&(o="360");i=e.match(/chrome\/([\d.]+)/)[1]}else"Firefox"===o?i=e.match(/firefox\/([\d.]+)/)[1]:"Opera"===o?i=e.match(/opera\/([\d.]+)/)[1]:"Safari"===o?i=e.match(/version\/([\d.]+)/)[1]:"Edge"===o?i=e.match(/edge\/([\d.]+)/)[1]:"QQBrowser"===o&&(i=e.match(/qqbrowser\/([\d.]+)/)[1]);t.type=o,t.version=parseInt(i)}return t}().type.toLowerCase();if("chrome"===e||"edge"===e){const e=this.player.$container;e.style.backdropFilter="blur(0px)",e.style.translateZ="0"}}play(){if(this.$videoElement){const e=this._getVideoReadyState();if(this.player.debug.log("Video",`play and readyState: ${e}`),0===e)return this.player.debug.warn("Video","readyState is 0 and set _delayPlay to true"),void(this._delayPlay=!0);this._play()}}_getVideoReadyState(){let e=0;return this.$videoElement&&(e=this.$videoElement.readyState),e}_play(){this.$videoElement&&this.$videoElement.play().then(()=>{this._delayPlay=!1,this.player.debug.log("Video","_play success"),setTimeout(()=>{this.isPlaying()||(this.player.debug.warn("Video","play failed and retry play"),this._play())},100)}).catch(e=>{this.player.debug.error("Video","_play error",e)})}pause(e){e?this.$videoElement&&this.$videoElement.pause():setTimeout(()=>{this.$videoElement&&this.$videoElement.pause()},100)}clearView(){}screenshot(e,t,i,o){e=e||Se(),o=o||J.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&J[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement;let n=this.$canvasElement;n.width=a.videoWidth,n.height=a.videoHeight,this.canvasContext.drawImage(a,0,0,n.width,n.height);const A=n.toDataURL(r[t]||r.png,s);if(this.canvasContext.clearRect(0,0,n.width,n.height),n.width=0,n.height=0,o===J.base64)return A;{const t=we(A);if(o===J.blob)return t;o===J.download&&Ne(t,e)}}initCanvasViewSize(){this.resize()}render(e){this.vwriter&&(this.vwriter.write(e.videoFrame),e.videoFrame.close())}resize(){let e=this.player.width,t=this.player.height;const i=this.player._opt,o=i.rotate;this.player.isControlBarShow()&&(Te()&&this.player.fullscreen&&i.useWebFullScreen?e-=H:t-=H),this.$videoElement.width=e,this.$videoElement.height=t,270!==o&&90!==o||(this.$videoElement.width=t,this.$videoElement.height=e);let r=(e-this.$videoElement.width)/2,s=(t-this.$videoElement.height)/2,a="contain";i.isResize||(a="fill"),i.isFullResize&&(a="none"),this.$videoElement.style.objectFit=a,this.$videoElement.style.transform="rotate("+o+"deg)",this.$videoElement.style.left=r+"px",this.$videoElement.style.top=s+"px"}isPlaying(){return this.$videoElement&&!this.$videoElement.paused}}class Ye{constructor(e){return new(Ye.getLoaderFactory(e._opt))(e)}static getLoaderFactory(e){return e.useMSE||e.useWCS&&!e.useOffscreen&&e.wcsUseVideoRender?ze:He}}class Xe extends Ve{constructor(e){super(),this.bufferList=[],this.player=e,this.scriptNode=null,this.hasInitScriptNode=!1,this.audioContextChannel=null,this.audioContext=new(window.AudioContext||window.webkitAudioContext),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.audioEnabled(!0),this.gainNode.gain.value=0,this._prevVolume=null,this.playing=!1,this.audioSyncVideoOption={diff:null},this.audioInfo={encType:"",channels:"",sampleRate:""},this.init=!1,this.hasAudio=!1,this.on(F.videoSyncAudio,e=>{this.audioSyncVideoOption=e}),this.player.debug.log("AudioContext","init")}resetInit(){this.init=!1,this.audioInfo={encType:"",channels:"",sampleRate:""}}async destroy(){this.closeAudio(),this.resetInit(),this.audioContext&&(await this.audioContext.close(),this.audioContext=null),this.gainNode=null,this.hasAudio=!1,this.playing=!1,this.scriptNode&&(this.scriptNode.onaudioprocess=ve,this.scriptNode=null),this.audioBufferSourceNode=null,this.mediaStreamAudioDestinationNode=null,this.hasInitScriptNode=!1,this.audioSyncVideoOption={diff:null},this._prevVolume=null,this.off(),this.player.debug.log("AudioContext","destroy")}updateAudioInfo(e){e.encTypeCode&&(this.audioInfo.encType=N[e.encTypeCode],this.audioInfo.encTypeCode=e.encTypeCode),e.channels&&(this.audioInfo.channels=e.channels),e.sampleRate&&(this.audioInfo.sampleRate=e.sampleRate),this.audioInfo.sampleRate&&this.audioInfo.channels&&this.audioInfo.encType&&!this.init&&(this.player.emit(F.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}initScriptNode(){if(this.playing=!0,this.hasInitScriptNode)return;const e=this.audioInfo.channels,t=this.audioContext.createScriptProcessor(1024,0,e);t.onaudioprocess=t=>{const i=t.outputBuffer;if(this.bufferList.length&&this.playing){if(!this.player._opt.useWCS&&!this.player._opt.useMSE&&this.player._opt.wasmDecodeAudioSyncVideo){if(this.audioSyncVideoOption.diff>re)return void this.player.debug.warn("AudioContext",`audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`);if(this.audioSyncVideoOption.diff<-1e3){this.player.debug.warn("AudioContext",`audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`);let e=this.bufferList.shift();for(;e.ts-this.player.videoTimestamp<-1e3&&this.bufferList.length>0;)e=this.bufferList.shift();if(0===this.bufferList.length)return}}if(0===this.bufferList.length)return;const t=this.bufferList.shift();t&&t.ts&&(this.player.audioTimestamp=t.ts);for(let o=0;o0?this.player.emit(F.mute,!1):this._prevVolume>0&&0===e&&this.player.emit(F.mute,!0),this.gainNode.gain.value=e,this.gainNode.gain.setValueAtTime(e,this.audioContext.currentTime),this.player.emit(F.volumechange,this.player.volume),this.player.emit(F.volume,this.player.volume),this._prevVolume=e)}closeAudio(){this.hasInitScriptNode&&(this.scriptNode&&this.scriptNode.disconnect(this.gainNode),this.gainNode&&this.gainNode.disconnect(this.audioContext.destination),this.gainNode&&this.gainNode.disconnect(this.mediaStreamAudioDestinationNode)),this.clear()}audioEnabled(e){e?"suspended"===this.audioContext.state&&this.audioContext.resume():"running"===this.audioContext.state&&this.audioContext.suspend()}isStateRunning(){return"running"===this.audioContext.state}isStateSuspended(){return"suspended"===this.audioContext.state}clear(){this.bufferList=[]}play(e,t){this.isMute||(this.hasAudio=!0,this.bufferList.push({buffer:e,ts:t}),this.bufferList.length>20&&(this.player.debug.warn("AudioContext",`bufferList is large: ${this.bufferList.length}`),this.bufferList.length>50&&this.bufferList.shift()))}pause(){this.audioSyncVideoOption={diff:null},this.playing=!1,this.clear()}resume(){this.playing=!0}getLastVolume(){return this._prevVolume}}class qe{constructor(e){return new(qe.getLoaderFactory())(e)}static getLoaderFactory(){return Xe}}class Ze extends Ve{constructor(e){super(),this.player=e,this.playing=!1,this.abortController=new AbortController,this.streamRate=ke(t=>{e.emit(F.kBps,(t/1e3).toFixed(2))}),e.debug.log("FetchStream","init")}async destroy(){this.abort(),this.off(),this.streamRate=null,this.player.debug.log("FetchStream","destroy")}fetchStream(e,t={}){const{demux:i}=this.player;this.player.debug.log("FetchStream","fetchStream",e,JSON.stringify(t)),this.player._times.streamStart=Se(),this.abortController||(this.abortController=new AbortController);const o=Object.assign({signal:this.abortController.signal},{headers:t.headers||{}});fetch(e,o).then(e=>{if(Oe(function(e){return e.ok&&e.status>=200&&e.status<=299}(e)))return this.player.debug.log("FetchStream",`fetch response status is ${e.status} and ok is ${e.ok} and emit error and next abort()`),this.abort(),this.emit(O.fetchError,`fetch response status is ${e.status} and ok is ${e.ok}`),void this.player.emit(F.error,O.fetchError);const t=e.body.getReader();this.emit(F.streamSuccess);const o=()=>{t.read().then(({done:e,value:t})=>{e?i.close():(this.streamRate&&this.streamRate(8*t.byteLength),i.dispatch(t),o())}).catch(e=>{i.close();const t=e.toString();-1===t.indexOf(ce)&&-1===t.indexOf(le)&&e.name!==ue&&(this.abort(),this.emit(O.fetchError,e),this.player.emit(F.error,O.fetchError))})};o()}).catch(e=>{"AbortError"!==e.name&&(i.close(),this.abort(),this.emit(O.fetchError,e),this.player.emit(F.error,O.fetchError))})}abort(){this.abortController&&(this.abortController.abort(),this.abortController=null)}}class Ke extends Ve{constructor(e){super(),this.player=e,this.socket=null,this.socketStatus=V,this.wsUrl=null,this.streamRate=ke(t=>{e.emit(F.kBps,(t/1e3).toFixed(2))}),e.debug.log("WebsocketLoader","init")}async destroy(){this.socket&&(this.socket.close(1e3,"Client disconnecting"),this.socket=null),this.socketStatus=V,this.streamRate=null,this.wsUrl=null,this.off(),this.player.debug.log("websocketLoader","destroy")}_createWebSocket(e={}){const t=this.player,{debug:i,events:{proxy:o},demux:r}=t,s=e.protocols||[];this.socket=new WebSocket(this.wsUrl,s),this.socket.binaryType="arraybuffer",o(this.socket,"open",()=>{this.emit(F.streamSuccess),i.log("websocketLoader","socket open"),this.socketStatus=U}),o(this.socket,"message",e=>{this.streamRate&&this.streamRate(8*e.data.byteLength),this._handleMessage(e.data)}),o(this.socket,"close",()=>{i.log("websocketLoader","socket close"),this.emit(F.streamEnd),this.socketStatus=Q}),o(this.socket,"error",e=>{i.log("websocketLoader","socket error"),this.emit(O.websocketError,e),this.player.emit(F.error,O.websocketError),this.socketStatus=W,r.close(),i.log("websocketLoader","socket error:",e)})}_handleMessage(e){const{demux:t}=this.player;t?t.dispatch(e):this.player.debug.warn("websocketLoader","websocket handle message demux is null")}fetchStream(e,t){this.player._times.streamStart=Se(),this.wsUrl=e,this._createWebSocket(t)}}class _e{constructor(e){return new(_e.getLoaderFactory(e._opt.protocol))(e)}static getLoaderFactory(e){return e===A?Ze:e===n?Ke:void 0}}var $e=i(function(t){function i(e,t){if(!e)throw"First parameter is required.";t=new o(e,t=t||{type:"video"});var s=this;function a(i){i&&(t.initCallback=function(){i(),i=t.initCallback=null});var o=new r(e,t);(h=new o(e,t)).record(),u("recording"),t.disableLogs||console.log("Initialized recorderType:",h.constructor.name,"for output-type:",t.type)}function n(e){if(e=e||function(){},h){if("paused"===s.state)return s.resumeRecording(),void setTimeout(function(){n(e)},1);"recording"===s.state||t.disableLogs||console.warn('Recording state should be: "recording", however current state is: ',s.state),t.disableLogs||console.log("Stopped recording "+t.type+" stream."),"gif"!==t.type?h.stop(i):(h.stop(),i()),u("stopped")}else g();function i(i){if(h){Object.keys(h).forEach(function(e){"function"!=typeof h[e]&&(s[e]=h[e])});var o=h.blob;if(!o){if(!i)throw"Recording failed.";h.blob=o=i}if(o&&!t.disableLogs&&console.log(o.type,"->",b(o.size)),e){var r;try{r=l.createObjectURL(o)}catch(e){}"function"==typeof e.call?e.call(s,r):e(r)}t.autoWriteToDisk&&d(function(e){var i={};i[t.type+"Blob"]=e,x.Store(i)})}else"function"==typeof e.call?e.call(s,""):e("")}}function A(e){postMessage((new FileReaderSync).readAsDataURL(e))}function d(e,i){if(!e)throw"Pass a callback function over getDataURL.";var o=i?i.blob:(h||{}).blob;if(!o)return t.disableLogs||console.warn("Blob encoder did not finish its job yet."),void setTimeout(function(){d(e,i)},1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var r=new FileReader;r.readAsDataURL(o),r.onload=function(t){e(t.target.result)}}else{var s=function(e){try{var t=l.createObjectURL(new Blob([e.toString(),"this.onmessage = function (eee) {"+e.name+"(eee.data);}"],{type:"application/javascript"})),i=new Worker(t);return l.revokeObjectURL(t),i}catch(e){}}(A);s.onmessage=function(t){e(t.data)},s.postMessage(o)}}function c(e){e=e||0,"paused"!==s.state?"stopped"!==s.state&&(e>=s.recordingDuration?n(s.onRecordingStopped):(e+=1e3,setTimeout(function(){c(e)},1e3))):setTimeout(function(){c(e)},1e3)}function u(e){s&&(s.state=e,"function"==typeof s.onStateChanged.call?s.onStateChanged.call(s,e):s.onStateChanged(e))}var h,p='It seems that recorder is destroyed or "startRecording" is not invoked for '+t.type+" recorder.";function g(){!0!==t.disableLogs&&console.warn(p)}var m={startRecording:function(i){return t.disableLogs||console.log("RecordRTC version: ",s.version),i&&(t=new o(e,i)),t.disableLogs||console.log("started recording "+t.type+" stream."),h?(h.clearRecordedData(),h.record(),u("recording"),s.recordingDuration&&c(),s):(a(function(){s.recordingDuration&&c()}),s)},stopRecording:n,pauseRecording:function(){h?"recording"===s.state?(u("paused"),h.pause(),t.disableLogs||console.log("Paused recording.")):t.disableLogs||console.warn("Unable to pause the recording. Recording state: ",s.state):g()},resumeRecording:function(){h?"paused"===s.state?(u("recording"),h.resume(),t.disableLogs||console.log("Resumed recording.")):t.disableLogs||console.warn("Unable to resume the recording. Recording state: ",s.state):g()},initRecorder:a,setRecordingDuration:function(e,t){if(void 0===e)throw"recordingDuration is required.";if("number"!=typeof e)throw"recordingDuration must be a number.";return s.recordingDuration=e,s.onRecordingStopped=t||function(){},{onRecordingStopped:function(e){s.onRecordingStopped=e}}},clearRecordedData:function(){h?(h.clearRecordedData(),t.disableLogs||console.log("Cleared old recorded data.")):g()},getBlob:function(){if(h)return h.blob;g()},getDataURL:d,toURL:function(){if(h)return l.createObjectURL(h.blob);g()},getInternalRecorder:function(){return h},save:function(e){h?y(h.blob,e):g()},getFromDisk:function(e){h?i.getFromDisk(t.type,e):g()},setAdvertisementArray:function(e){t.advertisement=[];for(var i=e.length,o=0;o-1&&"netscape"in window&&/ rv:/.test(navigator.userAgent),g=!h&&!u&&!!navigator.webkitGetUserMedia||v()||-1!==navigator.userAgent.toLowerCase().indexOf("chrome/"),m=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);m&&!g&&-1!==navigator.userAgent.indexOf("CriOS")&&(m=!1,g=!0);var f=window.MediaStream;function b(e){if(0===e)return"0 Bytes";var t=parseInt(Math.floor(Math.log(e)/Math.log(1e3)),10);return(e/Math.pow(1e3,t)).toPrecision(3)+" "+["Bytes","KB","MB","GB","TB"][t]}function y(e,t){if(!e)throw"Blob object is required.";if(!e.type)try{e.type="video/webm"}catch(e){}var i=(e.type||"video/webm").split("/")[1];if(-1!==i.indexOf(";")&&(i=i.split(";")[0]),t&&-1!==t.indexOf(".")){var o=t.split(".");t=o[0],i=o[1]}var r=(t||Math.round(9999999999*Math.random())+888888888)+"."+i;if(void 0!==navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(e,r);if(void 0!==navigator.msSaveBlob)return navigator.msSaveBlob(e,r);var s=document.createElement("a");s.href=l.createObjectURL(e),s.download=r,s.style="display:none;opacity:0;color:transparent;",(document.body||document.documentElement).appendChild(s),"function"==typeof s.click?s.click():(s.target="_blank",s.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))),l.revokeObjectURL(s.href)}function v(){return"undefined"!=typeof window&&"object"==typeof window.process&&"renderer"===window.process.type||(!("undefined"==typeof process||"object"!=typeof process.versions||!process.versions.electron)||"object"==typeof navigator&&"string"==typeof navigator.userAgent&&navigator.userAgent.indexOf("Electron")>=0)}function w(e,t){return e&&e.getTracks?e.getTracks().filter(function(e){return e.kind===(t||"audio")}):[]}function S(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}void 0===f&&"undefined"!=typeof webkitMediaStream&&(f=webkitMediaStream),void 0!==f&&void 0===f.prototype.stop&&(f.prototype.stop=function(){this.getTracks().forEach(function(e){e.stop()})}),void 0!==i&&(i.invokeSaveAsDialog=y,i.getTracks=w,i.getSeekableBlob=function(e,t){if("undefined"==typeof EBML)throw new Error("Please link: https://www.webrtc-experiment.com/EBML.js");var i=new EBML.Reader,o=new EBML.Decoder,r=EBML.tools,s=new FileReader;s.onload=function(e){o.decode(this.result).forEach(function(e){i.read(e)}),i.stop();var s=r.makeMetadataSeekable(i.metadatas,i.duration,i.cues),a=this.result.slice(i.metadataSize),n=new Blob([s,a],{type:"video/webm"});t(n)},s.readAsArrayBuffer(e)},i.bytesToSize=b,i.isElectron=v);var E={};function B(){if(p||m||u)return!0;var e,t,i=navigator.userAgent,o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10);return(g||h)&&(e=i.indexOf("Chrome"),o=i.substring(e+7)),-1!==(t=o.indexOf(";"))&&(o=o.substring(0,t)),-1!==(t=o.indexOf(" "))&&(o=o.substring(0,t)),r=parseInt(""+o,10),isNaN(r)&&(o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),r>=49}function C(e,t){var i=this;if(void 0===e)throw'First argument "MediaStream" is required.';if("undefined"==typeof MediaRecorder)throw"Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";if("audio"===(t=t||{mimeType:"video/webm"}).type){var o;if(w(e,"video").length&&w(e,"audio").length)navigator.mozGetUserMedia?(o=new f).addTrack(w(e,"audio")[0]):o=new f(w(e,"audio")),e=o;t.mimeType&&-1!==t.mimeType.toString().toLowerCase().indexOf("audio")||(t.mimeType=g?"audio/webm":"audio/ogg"),t.mimeType&&"audio/ogg"!==t.mimeType.toString().toLowerCase()&&navigator.mozGetUserMedia&&(t.mimeType="audio/ogg")}var r,s=[];function a(){i.timestamps.push((new Date).getTime()),"function"==typeof t.onTimeStamp&&t.onTimeStamp(i.timestamps[i.timestamps.length-1],i.timestamps)}function n(e){return r&&r.mimeType?r.mimeType:e.mimeType||"video/webm"}function A(){s=[],r=null,i.timestamps=[]}this.getArrayOfBlobs=function(){return s},this.record=function(){i.blob=null,i.clearRecordedData(),i.timestamps=[],d=[],s=[];var o=t;t.disableLogs||console.log("Passing following config over MediaRecorder API.",o),r&&(r=null),g&&!B()&&(o="video/vp8"),"function"==typeof MediaRecorder.isTypeSupported&&o.mimeType&&(MediaRecorder.isTypeSupported(o.mimeType)||(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType),o.mimeType="audio"===t.type?"audio/webm":"video/webm"));try{r=new MediaRecorder(e,o),t.mimeType=o.mimeType}catch(t){r=new MediaRecorder(e)}o.mimeType&&!MediaRecorder.isTypeSupported&&"canRecordMimeType"in r&&!1===r.canRecordMimeType(o.mimeType)&&(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType)),r.ondataavailable=function(e){if(e.data&&d.push("ondataavailable: "+b(e.data.size)),"number"!=typeof t.timeSlice)!e.data||!e.data.size||e.data.size<100||i.blob?i.recordingCallback&&(i.recordingCallback(new Blob([],{type:n(o)})),i.recordingCallback=null):(i.blob=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)}),i.recordingCallback&&(i.recordingCallback(i.blob),i.recordingCallback=null));else if(e.data&&e.data.size&&(s.push(e.data),a(),"function"==typeof t.ondataavailable)){var r=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)});t.ondataavailable(r)}},r.onstart=function(){d.push("started")},r.onpause=function(){d.push("paused")},r.onresume=function(){d.push("resumed")},r.onstop=function(){d.push("stopped")},r.onerror=function(e){e&&(e.name||(e.name="UnknownError"),d.push("error: "+e),t.disableLogs||(-1!==e.name.toString().toLowerCase().indexOf("invalidstate")?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.",e):-1!==e.name.toString().toLowerCase().indexOf("notsupported")?console.error("MIME type (",o.mimeType,") is not supported.",e):-1!==e.name.toString().toLowerCase().indexOf("security")?console.error("MediaRecorder security error",e):"OutOfMemory"===e.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"IllegalStreamModification"===e.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"OtherRecordingError"===e.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"GenericError"===e.name?console.error("The UA cannot provide the codec or recording option that has been requested.",e):console.error("MediaRecorder Error",e)),function(){if(!i.manuallyStopped&&r&&"inactive"===r.state)return delete t.timeslice,void r.start(6e5);setTimeout(void 0,1e3)}(),"inactive"!==r.state&&"stopped"!==r.state&&r.stop())},"number"==typeof t.timeSlice?(a(),r.start(t.timeSlice)):r.start(36e5),t.initCallback&&t.initCallback()},this.timestamps=[],this.stop=function(e){e=e||function(){},i.manuallyStopped=!0,r&&(this.recordingCallback=e,"recording"===r.state&&r.stop(),"number"==typeof t.timeSlice&&setTimeout(function(){i.blob=new Blob(s,{type:n(t)}),i.recordingCallback(i.blob)},100))},this.pause=function(){r&&"recording"===r.state&&r.pause()},this.resume=function(){r&&"paused"===r.state&&r.resume()},this.clearRecordedData=function(){r&&"recording"===r.state&&i.stop(A),A()},this.getInternalRecorder=function(){return r},this.blob=null,this.getState=function(){return r&&r.state||"inactive"};var d=[];this.getAllStates=function(){return d},void 0===t.checkForInactiveTracks&&(t.checkForInactiveTracks=!1);i=this;!function o(){if(r&&!1!==t.checkForInactiveTracks)return!1===function(){if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}()?(t.disableLogs||console.log("MediaStream seems stopped."),void i.stop()):void setTimeout(o,1e3)}(),this.name="MediaStreamRecorder",this.toString=function(){return this.name}}function R(e,t){if(!w(e,"audio").length)throw"Your stream has no audio tracks.";var o,r=this,s=[],a=[],n=!1,A=0,d=2,c=(t=t||{}).desiredSampRate;function u(){if(!1===t.checkForInactiveTracks)return!0;if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}function h(e,t){function i(e,t){var i,o=e.numberOfAudioChannels,r=e.leftBuffers.slice(0),s=e.rightBuffers.slice(0),a=e.sampleRate,n=e.internalInterleavedLength,A=e.desiredSampRate;function d(e,t,i){var o=Math.round(e.length*(t/i)),r=[],s=Number((e.length-1)/(o-1));r[0]=e[0];for(var a=1;a96e3)&&(t.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),t.disableLogs||t.desiredSampRate&&console.log("Desired sample-rate: "+t.desiredSampRate);var y=!1;function v(){s=[],a=[],A=0,E=!1,n=!1,y=!1,p=null,r.leftchannel=s,r.rightchannel=a,r.numberOfAudioChannels=d,r.desiredSampRate=c,r.sampleRate=b,r.recordingLength=A,B={left:[],right:[],recordingLength:0}}function S(){o&&(o.onaudioprocess=null,o.disconnect(),o=null),g&&(g.disconnect(),g=null),v()}this.pause=function(){y=!0},this.resume=function(){if(!1===u())throw"Please make sure MediaStream is active.";if(!n)return t.disableLogs||console.log("Seems recording has been restarted."),void this.record();y=!1},this.clearRecordedData=function(){t.checkForInactiveTracks=!1,n&&this.stop(S),S()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var E=!1;o.onaudioprocess=function(e){if(!y)if(!1===u()&&(t.disableLogs||console.log("MediaStream seems stopped."),o.disconnect(),n=!1),n){E||(E=!0,t.onAudioProcessStarted&&t.onAudioProcessStarted(),t.initCallback&&t.initCallback());var i=e.inputBuffer.getChannelData(0),c=new Float32Array(i);if(s.push(c),2===d){var l=e.inputBuffer.getChannelData(1),h=new Float32Array(l);a.push(h)}A+=f,r.recordingLength=A,void 0!==t.timeSlice&&(B.recordingLength+=f,B.left.push(c),2===d&&B.right.push(h))}else g&&(g.disconnect(),g=null)},p.createMediaStreamDestination?o.connect(p.createMediaStreamDestination()):o.connect(p.destination),this.leftchannel=s,this.rightchannel=a,this.numberOfAudioChannels=d,this.desiredSampRate=c,this.sampleRate=b,r.recordingLength=A;var B={left:[],right:[],recordingLength:0};function C(){n&&"function"==typeof t.ondataavailable&&void 0!==t.timeSlice&&(B.left.length?(h({desiredSampRate:c,sampleRate:b,numberOfAudioChannels:d,internalInterleavedLength:B.recordingLength,leftBuffers:B.left,rightBuffers:1===d?[]:B.right},function(e,i){var o=new Blob([i],{type:"audio/wav"});t.ondataavailable(o),setTimeout(C,t.timeSlice)}),B={left:[],right:[],recordingLength:0}):setTimeout(C,t.timeSlice))}}function k(e,t){if("undefined"==typeof html2canvas)throw"Please link: https://www.webrtc-experiment.com/screenshot.js";(t=t||{}).frameInterval||(t.frameInterval=10);var i=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(e){e in document.createElement("canvas")&&(i=!0)});var o,r,s,a=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),n=50,A=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);if(a&&A&&A[2]&&(n=parseInt(A[2],10)),a&&n<52&&(i=!1),t.useWhammyRecorder&&(i=!1),i)if(t.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),e instanceof HTMLCanvasElement)o=e;else{if(!(e instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";o=e.canvas}else navigator.mozGetUserMedia&&(t.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));this.record=function(){if(s=!0,i&&!t.useWhammyRecorder){var e;"captureStream"in o?e=o.captureStream(25):"mozCaptureStream"in o?e=o.mozCaptureStream(25):"webkitCaptureStream"in o&&(e=o.webkitCaptureStream(25));try{var a=new f;a.addTrack(w(e,"video")[0]),e=a}catch(e){}if(!e)throw"captureStream API are NOT available.";(r=new C(e,{mimeType:t.mimeType||"video/webm"})).record()}else h.frames=[],u=(new Date).getTime(),l();t.initCallback&&t.initCallback()},this.getWebPImages=function(i){if("canvas"===e.nodeName.toLowerCase()){var o=h.frames.length;h.frames.forEach(function(e,i){var r=o-i;t.disableLogs||console.log(r+"/"+o+" frames remaining"),t.onEncodingCallback&&t.onEncodingCallback(r,o);var s=e.image.toDataURL("image/webp",1);h.frames[i].image=s}),t.disableLogs||console.log("Generating WebM"),i()}else i()},this.stop=function(e){s=!1;var o=this;i&&r?r.stop(e):this.getWebPImages(function(){h.compile(function(i){t.disableLogs||console.log("Recording finished!"),o.blob=i,o.blob.forEach&&(o.blob=new Blob([],{type:"video/webm"})),e&&e(o.blob),h.frames=[]})})};var d=!1;function c(){h.frames=[],s=!1,d=!1}function l(){if(d)return u=(new Date).getTime(),setTimeout(l,500);if("canvas"===e.nodeName.toLowerCase()){var i=(new Date).getTime()-u;return u=(new Date).getTime(),h.frames.push({image:(o=document.createElement("canvas"),r=o.getContext("2d"),o.width=e.width,o.height=e.height,r.drawImage(e,0,0),o),duration:i}),void(s&&setTimeout(l,t.frameInterval))}var o,r;html2canvas(e,{grabMouse:void 0===t.showMousePointer||t.showMousePointer,onrendered:function(e){var i=(new Date).getTime()-u;if(!i)return setTimeout(l,t.frameInterval);u=(new Date).getTime(),h.frames.push({image:e.toDataURL("image/webp",1),duration:i}),s&&setTimeout(l,t.frameInterval)}})}this.pause=function(){d=!0,r instanceof C&&r.pause()},this.resume=function(){d=!1,r instanceof C?r.resume():s||this.record()},this.clearRecordedData=function(){s&&this.stop(c),c()},this.name="CanvasRecorder",this.toString=function(){return this.name};var u=(new Date).getTime(),h=new I.Video(100)}function T(e,t){function i(e){e=void 0!==e?e:10;var t=(new Date).getTime()-A;return t?s?(A=(new Date).getTime(),setTimeout(i,100)):(A=(new Date).getTime(),n.paused&&n.play(),l.drawImage(n,0,0,c.width,c.height),d.frames.push({duration:t,image:c.toDataURL("image/webp")}),void(r||setTimeout(i,e,e))):setTimeout(i,e,e)}function o(e,t,i,o,r){var s=document.createElement("canvas");s.width=c.width,s.height=c.height;var a,n,A,d=s.getContext("2d"),l=[],u=-1===t,h=t&&t>0&&t<=e.length?t:e.length,p=0,g=0,m=0,f=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),b=i&&i>=0&&i<=1?i:0,y=o&&o>=0&&o<=1?o:0,v=!1;n=-1,A=(a={length:h,functionToLoop:function(t,i){var o,r,s,a=function(){!v&&s-o<=s*y||(u&&(v=!0),l.push(e[i])),t()};if(v)a();else{var n=new Image;n.onload=function(){d.drawImage(n,0,0,c.width,c.height);var e=d.getImageData(0,0,c.width,c.height);o=0,r=e.data.length,s=e.data.length/4;for(var t=0;t127)throw"TrackNumber > 127 not supported";return[128|e.trackNum,e.timecode>>8,255&e.timecode,t].map(function(e){return String.fromCharCode(e)}).join("")+e.frame}({discardable:0,frame:e.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(t)});return t+=e.duration,{data:i,id:163}}))}function i(e){for(var t=[];e>0;)t.push(255&e),e>>=8;return new Uint8Array(t.reverse())}function o(e){return new Uint8Array(e.split("").map(function(e){return e.charCodeAt(0)}))}function r(e){var t=[];e=(e.length%8?new Array(9-e.length%8).join("0"):"")+e;for(var i=0;i1?2*i[0].width:i[0].width;var o=1;3!==e&&4!==e||(o=2),5!==e&&6!==e||(o=3),7!==e&&8!==e||(o=4),9!==e&&10!==e||(o=5),a.height=i[0].height*o}else a.width=A.width||360,a.height=A.height||240;t&&t instanceof HTMLVideoElement&&p(t),i.forEach(function(e,t){p(e,t)}),setTimeout(h,A.frameInterval)}}function p(e,t){if(!s){var i=0,o=0,r=e.width,a=e.height;1===t&&(i=e.width),2===t&&(o=e.height),3===t&&(i=e.width,o=e.height),4===t&&(o=2*e.height),5===t&&(i=e.width,o=2*e.height),6===t&&(o=3*e.height),7===t&&(i=e.width,o=3*e.height),void 0!==e.stream.left&&(i=e.stream.left),void 0!==e.stream.top&&(o=e.stream.top),void 0!==e.stream.width&&(r=e.stream.width),void 0!==e.stream.height&&(a=e.stream.height),n.drawImage(e,i,o,r,a),"function"==typeof e.stream.onRender&&e.stream.onRender(n,i,o,r,a,t)}}function g(e){var t=document.createElement("video");return function(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}(e,t),t.className=o,t.muted=!0,t.volume=0,t.width=e.width||A.width||360,t.height=e.height||A.height||240,t.play(),t}function m(e){r=[],(e=e||t).forEach(function(e){if(e.getTracks().filter(function(e){return"video"===e.kind}).length){var t=g(e);t.stream=e,r.push(t)}})}void 0!==d?u.AudioContext=d:"undefined"!=typeof webkitAudioContext&&(u.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){h()},this.appendStreams=function(e){if(!e)throw"First parameter is required.";e instanceof Array||(e=[e]),e.forEach(function(e){var i=new l;if(e.getTracks().filter(function(e){return"video"===e.kind}).length){var o=g(e);o.stream=e,r.push(o),i.addTrack(e.getTracks().filter(function(e){return"video"===e.kind})[0])}if(e.getTracks().filter(function(e){return"audio"===e.kind}).length){var s=A.audioContext.createMediaStreamSource(e);A.audioDestination=A.audioContext.createMediaStreamDestination(),s.connect(A.audioDestination),i.addTrack(A.audioDestination.stream.getTracks().filter(function(e){return"audio"===e.kind})[0])}t.push(i)})},this.releaseStreams=function(){r=[],s=!0,A.gainNode&&(A.gainNode.disconnect(),A.gainNode=null),A.audioSources.length&&(A.audioSources.forEach(function(e){e.disconnect()}),A.audioSources=[]),A.audioDestination&&(A.audioDestination.disconnect(),A.audioDestination=null),A.audioContext&&A.audioContext.close(),A.audioContext=null,n.clearRect(0,0,a.width,a.height),a.stream&&(a.stream.stop(),a.stream=null)},this.resetVideoStreams=function(e){!e||e instanceof Array||(e=[e]),m(e)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=function(){s=!1;var e=function(){var e;m(),"captureStream"in a?e=a.captureStream():"mozCaptureStream"in a?e=a.mozCaptureStream():A.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var t=new l;return e.getTracks().filter(function(e){return"video"===e.kind}).forEach(function(e){t.addTrack(e)}),a.stream=t,t}(),i=function(){u.AudioContextConstructor||(u.AudioContextConstructor=new u.AudioContext);A.audioContext=u.AudioContextConstructor,A.audioSources=[],!0===A.useGainNode&&(A.gainNode=A.audioContext.createGain(),A.gainNode.connect(A.audioContext.destination),A.gainNode.gain.value=0);var e=0;if(t.forEach(function(t){if(t.getTracks().filter(function(e){return"audio"===e.kind}).length){e++;var i=A.audioContext.createMediaStreamSource(t);!0===A.useGainNode&&i.connect(A.gainNode),A.audioSources.push(i)}}),!e)return;return A.audioDestination=A.audioContext.createMediaStreamDestination(),A.audioSources.forEach(function(e){e.connect(A.audioDestination)}),A.audioDestination.stream}();return i&&i.getTracks().filter(function(e){return"audio"===e.kind}).forEach(function(t){e.addTrack(t)}),t.forEach(function(e){e.fullcanvas}),e}}function L(e,t){e=e||[];var i,o,r=this;(t=t||{elementClass:"multi-streams-mixer",mimeType:"video/webm",video:{width:360,height:240}}).frameInterval||(t.frameInterval=10),t.video||(t.video={}),t.video.width||(t.video.width=360),t.video.height||(t.video.height=240),this.record=function(){var r;i=new j(e,t.elementClass||"multi-streams-mixer"),(r=[],e.forEach(function(e){w(e,"video").forEach(function(e){r.push(e)})}),r).length&&(i.frameInterval=t.frameInterval||10,i.width=t.video.width||360,i.height=t.video.height||240,i.startDrawingFrames()),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()),(o=new C(i.getMixedStream(),t)).record()},this.stop=function(e){o&&o.stop(function(t){r.blob=t,e(t),r.clearRecordedData()})},this.pause=function(){o&&o.pause()},this.resume=function(){o&&o.resume()},this.clearRecordedData=function(){o&&(o.clearRecordedData(),o=null),i&&(i.releaseStreams(),i=null)},this.addStreams=function(r){if(!r)throw"First parameter is required.";r instanceof Array||(r=[r]),e.concat(r),o&&i&&(i.appendStreams(r),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()))},this.resetVideoStreams=function(e){i&&(!e||e instanceof Array||(e=[e]),i.resetVideoStreams(e))},this.getMixer=function(){return i},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function F(e,t){var i,o,r;function s(){return new ReadableStream({start:function(o){var r=document.createElement("canvas"),s=document.createElement("video"),a=!0;s.srcObject=e,s.muted=!0,s.height=t.height,s.width=t.width,s.volume=0,s.onplaying=function(){r.width=t.width,r.height=t.height;var e=r.getContext("2d"),n=1e3/t.frameRate,A=setInterval(function(){if(i&&(clearInterval(A),o.close()),a&&(a=!1,t.onVideoProcessStarted&&t.onVideoProcessStarted()),e.drawImage(s,0,0),"closed"!==o._controlledReadableStream.state)try{o.enqueue(e.getImageData(0,0,t.width,t.height))}catch(e){}},n)},s.play()}})}function a(e,A){if(!t.workerPath&&!A)return i=!1,void fetch("https://unpkg.com/webm-wasm@latest/dist/webm-worker.js").then(function(t){t.arrayBuffer().then(function(t){a(e,t)})});if(!t.workerPath&&A instanceof ArrayBuffer){var d=new Blob([A],{type:"text/javascript"});t.workerPath=l.createObjectURL(d)}t.workerPath||console.error("workerPath parameter is missing."),(o=new Worker(t.workerPath)).postMessage(t.webAssemblyPath||"https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm"),o.addEventListener("message",function(e){"READY"===e.data?(o.postMessage({width:t.width,height:t.height,bitrate:t.bitrate||1200,timebaseDen:t.frameRate||30,realtime:t.realtime}),s().pipeTo(new WritableStream({write:function(e){i?console.error("Got image, but recorder is finished!"):o.postMessage(e.data.buffer,[e.data.buffer])}}))):e.data&&(r||n.push(e.data))})}"undefined"!=typeof ReadableStream&&"undefined"!=typeof WritableStream||console.error("Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js"),(t=t||{}).width=t.width||640,t.height=t.height||480,t.frameRate=t.frameRate||30,t.bitrate=t.bitrate||1200,t.realtime=t.realtime||!0,this.record=function(){n=[],r=!1,this.blob=null,a(e),"function"==typeof t.initCallback&&t.initCallback()},this.pause=function(){r=!0},this.resume=function(){r=!1};var n=[];this.stop=function(e){i=!0;var t=this;!function(e){o?(o.addEventListener("message",function(t){null===t.data&&(o.terminate(),o=null,e&&e())}),o.postMessage(null)):e&&e()}(function(){t.blob=new Blob(n,{type:"video/webm"}),e(t.blob)})},this.name="WebAssemblyRecorder",this.toString=function(){return this.name},this.clearRecordedData=function(){n=[],r=!1,this.blob=null},this.blob=null}void 0!==i&&(i.DiskStorage=x),void 0!==i&&(i.GifRecorder=D),void 0===i&&(t.exports=j),void 0!==i&&(i.MultiStreamRecorder=L),void 0!==i&&(i.RecordRTCPromisesHandler=function(e,t){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if(void 0===e)throw'First argument "MediaStream" is required.';var o=this;o.recordRTC=new i(e,t),this.startRecording=function(){return new Promise(function(e,t){try{o.recordRTC.startRecording(),e()}catch(e){t(e)}})},this.stopRecording=function(){return new Promise(function(e,t){try{o.recordRTC.stopRecording(function(i){o.blob=o.recordRTC.getBlob(),o.blob&&o.blob.size?e(i):t("Empty blob.",o.blob)})}catch(e){t(e)}})},this.pauseRecording=function(){return new Promise(function(e,t){try{o.recordRTC.pauseRecording(),e()}catch(e){t(e)}})},this.resumeRecording=function(){return new Promise(function(e,t){try{o.recordRTC.resumeRecording(),e()}catch(e){t(e)}})},this.getDataURL=function(e){return new Promise(function(e,t){try{o.recordRTC.getDataURL(function(t){e(t)})}catch(e){t(e)}})},this.getBlob=function(){return new Promise(function(e,t){try{e(o.recordRTC.getBlob())}catch(e){t(e)}})},this.getInternalRecorder=function(){return new Promise(function(e,t){try{e(o.recordRTC.getInternalRecorder())}catch(e){t(e)}})},this.reset=function(){return new Promise(function(e,t){try{e(o.recordRTC.reset())}catch(e){t(e)}})},this.destroy=function(){return new Promise(function(e,t){try{e(o.recordRTC.destroy())}catch(e){t(e)}})},this.getState=function(){return new Promise(function(e,t){try{e(o.recordRTC.getState())}catch(e){t(e)}})},this.blob=null,this.version="5.6.2"}),void 0!==i&&(i.WebAssemblyRecorder=F)});class et extends Ve{constructor(e){super(),this.player=e,this.fileName="",this.fileType=e._opt.recordType||u,this.isRecording=!1,this.recordingTimestamp=0,this.recordingInterval=null,this.recorder=null,e.debug.log("Recorder","init")}destroy(){this._reset(),this.player.debug.log("Recorder","destroy")}setFileName(e,t){this.fileName=e,l!==t&&u!==t||(this.fileType=t)}get recording(){return this.isRecording}get recordTime(){return this.recordingTimestamp}startRecord(){const e=this.player.debug,t={type:"video",mimeType:"video/webm;codecs=h264",onTimeStamp:t=>{e.log("Recorder","record timestamp :"+t)},disableLogs:!this.player._opt.debug};try{const e=this.player.video.$videoElement.captureStream(25);if(this.player.audio&&this.player.audio.mediaStreamAudioDestinationNode&&this.player.audio.mediaStreamAudioDestinationNode.stream&&!this.player.audio.isStateSuspended()&&this.player.audio.hasAudio&&this.player._opt.hasAudio){const t=this.player.audio.mediaStreamAudioDestinationNode.stream;if(t.getAudioTracks().length>0){const i=t.getAudioTracks()[0];i&&i.enabled&&e.addTrack(i)}}this.recorder=$e(e,t)}catch(t){e.error("Recorder","startRecord error",t),this.emit(F.recordCreateError)}this.recorder&&(this.isRecording=!0,this.player.emit(F.recording,!0),this.recorder.startRecording(),e.log("Recorder","start recording"),this.player.emit(F.recordStart),this.recordingInterval=window.setInterval(()=>{this.recordingTimestamp+=1,this.player.emit(F.recordingTimestamp,this.recordingTimestamp)},1e3))}stopRecordAndSave(){this.recorder&&this.isRecording&&this.recorder.stopRecording(()=>{this.player.debug.log("Recorder","stop recording"),this.player.emit(F.recordEnd);const e=(this.fileName||Se())+"."+(this.fileType||u);Ne(this.recorder.getBlob(),e),this._reset(),this.player.emit(F.recording,!1)})}_reset(){this.isRecording=!1,this.recordingTimestamp=0,this.recorder&&(this.recorder.destroy(),this.recorder=null),this.fileName=null,this.recordingInterval&&clearInterval(this.recordingInterval),this.recordingInterval=null}}class tt{constructor(e){return new(tt.getLoaderFactory())(e)}static getLoaderFactory(){return et}}class it{constructor(e){this.player=e,this.decoderWorker=new Worker(e._opt.decoder),this._initDecoderWorker(),e.debug.log("decoderWorker","init")}async destroy(){this.decoderWorker&&(this.decoderWorker.postMessage({cmd:j}),this.decoderWorker.terminate(),this.decoderWorker=null),this.player.debug.log("decoderWorker","destroy")}_initDecoderWorker(){const{debug:e,events:{proxy:t}}=this.player;this.decoderWorker.onmessage=t=>{const i=t.data;switch(i.cmd){case m:e.log("decoderWorker","onmessage:",m),this.player.loaded||this.player.emit(F.load),this.player.emit(F.decoderWorkerInit),this._initWork();break;case S:e.log("decoderWorker","onmessage:",S,i.code),this.player._times.decodeStart||(this.player._times.decodeStart=Se()),this.player.video.updateVideoInfo({encTypeCode:i.code});break;case w:e.log("decoderWorker","onmessage:",w,i.code),this.player.audio&&this.player.audio.updateAudioInfo({encTypeCode:i.code});break;case f:if(e.log("decoderWorker","onmessage:",f,`width:${i.w},height:${i.h}`),this.player.video.updateVideoInfo({width:i.w,height:i.h}),!this.player._opt.openWebglAlignment&&i.w/2%4!=0)return void this.player.emit(O.webglAlignmentError);this.player.video.initCanvasViewSize();break;case v:e.log("decoderWorker","onmessage:",v,`channels:${i.channels},sampleRate:${i.sampleRate}`),this.player.audio&&(this.player.audio.updateAudioInfo(i),this.player.audio.initScriptNode(i));break;case b:this.player.handleRender(),this.player.video.render(i),this.player.emit(F.timeUpdate,i.ts),this.player.updateStats({fps:!0,ts:i.ts,buf:i.delay}),this.player._times.videoStart||(this.player._times.videoStart=Se(),this.player.handlePlayToRenderTimes());break;case y:this.player.playing&&this.player.audio&&this.player.audio.play(i.buffer,i.ts);break;case E:i.message&&-1!==i.message.indexOf(B)&&this.player.emitError(O.wasmDecodeError);break;default:this.player[i.cmd]&&this.player[i.cmd](i)}}}_initWork(){const e={debug:this.player._opt.debug,useOffscreen:this.player._opt.useOffscreen,useWCS:this.player._opt.useWCS,videoBuffer:this.player._opt.videoBuffer,videoBufferDelay:this.player._opt.videoBufferDelay,openWebglAlignment:this.player._opt.openWebglAlignment};this.decoderWorker.postMessage({cmd:I,opt:JSON.stringify(e),sampleRate:this.player.audio&&this.player.audio.audioContext.sampleRate||0})}decodeVideo(e,t,i){const o={type:R,ts:Math.max(t,0),isIFrame:i};this.decoderWorker.postMessage({cmd:x,buffer:e,options:o},[e.buffer])}decodeAudio(e,t){this.player._opt.useWCS||this.player._opt.useMSE?this._decodeAudioNoDelay(e,t):this._decodeAudio(e,t)}_decodeAudio(e,t){const i={type:C,ts:Math.max(t,0)};this.decoderWorker.postMessage({cmd:x,buffer:e,options:i},[e.buffer])}_decodeAudioNoDelay(e,t){this.decoderWorker.postMessage({cmd:D,buffer:e,ts:Math.max(t,0)},[e.buffer])}updateWorkConfig(e){this.decoderWorker.postMessage({cmd:L,key:e.key,value:e.value})}}class ot extends Ve{constructor(e){super(),this.player=e,this.stopId=null,this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.initInterval()}destroy(){this.stopId&&(clearInterval(this.stopId),this.stopId=null),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.off(),this.player.debug.log("CommonDemux","destroy")}getDelay(e){if(!e)return-1;if(this.firstTimestamp){if(e){const t=Date.now()-this.startTimestamp,i=e-this.firstTimestamp;this.delay=t>=i?t-i:i-t}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay}resetDelay(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1}initInterval(){this.player.debug.log("common dumex","init Interval");let e=()=>{let e;const t=this.player._opt.videoBuffer,i=this.player._opt.videoBufferDelay;if(!this.player.isDestroyedOrClosed())if(this.player._opt.useMSE&&this.player.mseDecoder&&this.player.mseDecoder.getSourceBufferUpdating())this.player.debug.warn("CommonDemux",`_loop getSourceBufferUpdating is true and bufferList length is ${this.bufferList.length}`);else if(this.bufferList.length)if(this.dropping){for(e=this.bufferList.shift(),e.type===C&&0===e.payload[1]&&this._doDecoderDecode(e);!e.isIFrame&&this.bufferList.length;)e=this.bufferList.shift(),e.type===C&&0===e.payload[1]&&this._doDecoderDecode(e);e.isIFrame&&this.getDelay(e.ts)<=Math.min(t,200)&&(this.dropping=!1,this._doDecoderDecode(e))}else e=this.bufferList[0],-1===this.getDelay(e.ts)?(this.bufferList.shift(),this._doDecoderDecode(e)):this.delay>t+i?(this.resetDelay(),this.dropping=!0):(e=this.bufferList[0],this.getDelay(e.ts)>t&&(this.bufferList.shift(),this._doDecoderDecode(e)))};e(),this.stopId=setInterval(e,10)}_doDecode(e,t,i,o,r){const s=this.player;let a={ts:i,cts:r,type:t,isIFrame:!1};s._opt.useWCS&&!s._opt.useOffscreen||s._opt.useMSE?(t===R&&(a.isIFrame=o),this.pushBuffer(e,a)):t===R?s.decoderWorker&&s.decoderWorker.decodeVideo(e,i,o):t===C&&s._opt.hasAudio&&s.decoderWorker&&s.decoderWorker.decodeAudio(e,i)}_doDecoderDecode(e){const t=this.player,{webcodecsDecoder:i,mseDecoder:o}=t;e.type===C?t._opt.hasAudio&&t.decoderWorker&&t.decoderWorker.decodeAudio(e.payload,e.ts):e.type===R&&(t._opt.useWCS&&!t._opt.useOffscreen?i.decodeVideo(e.payload,e.ts,e.isIFrame):t._opt.useMSE&&o.decodeVideo(e.payload,e.ts,e.isIFrame,e.cts))}pushBuffer(e,t){t.type===C?this.bufferList.push({ts:t.ts,payload:e,type:C}):t.type===R&&this.bufferList.push({ts:t.ts,cts:t.cts,payload:e,type:R,isIFrame:t.isIFrame})}close(){}_decodeEnhancedH265Video(e,t){const i=e[0],o=48&i,r=15&i,s=e.slice(1,5),a=new ArrayBuffer(4),n=new Uint32Array(a),A="a"==String.fromCharCode(s[0]);if(r===he){if(o===me){const t=e.slice(5);if(!A){const e=new Uint8Array(5+t.length);e.set([28,0,0,0,0],0),e.set(t,5),this._doDecode(e,R,0,!0,0)}}}else if(r===pe){let i=e,r=0;const s=o===me;if(!A){n[0]=e[4],n[1]=e[3],n[2]=e[2],n[3]=0,r=n[0];i=Me(e.slice(8),s),this._doDecode(i,R,t,s,r)}}else if(r===ge){const i=o===me;let r=Me(e.slice(5),i);this._doDecode(r,R,t,i,0)}}_isEnhancedH265Header(e){return!(128&~e)}}class rt extends ot{constructor(e){super(e),this.input=this._inputFlv(),this.flvDemux=this.dispatchFlvData(this.input),e.debug.log("FlvDemux","init")}destroy(){super.destroy(),this.input=null,this.flvDemux=null,this.player.debug.log("FlvDemux","destroy")}dispatch(e){this.flvDemux(e)}*_inputFlv(){yield 9;const e=new ArrayBuffer(4),t=new Uint8Array(e),i=new Uint32Array(e),o=this.player;for(;;){t[3]=0;const e=yield 15,r=e[4];t[0]=e[7],t[1]=e[6],t[2]=e[5];const s=i[0];t[0]=e[10],t[1]=e[9],t[2]=e[8];let a=i[0];16777215===a&&(t[3]=e[11],a=i[0]);const n=yield s;switch(r){case k:o._opt.hasAudio&&(o.updateStats({abps:n.byteLength}),n.byteLength>0&&this._doDecode(n,C,a));break;case T:if(o._times.demuxStart||(o._times.demuxStart=Se()),o._opt.hasVideo){o.updateStats({vbps:n.byteLength});const e=n[0];if(this._isEnhancedH265Header(e))this._decodeEnhancedH265Video(n,a);else{const e=n[0]>>4==1;if(n.byteLength>0){i[0]=n[4],i[1]=n[3],i[2]=n[2],i[3]=0;let t=i[0];this._doDecode(n,R,a,e,t)}}}}}}dispatchFlvData(e){let t=e.next(),i=null;return o=>{let r=new Uint8Array(o);if(i){let e=new Uint8Array(i.length+r.length);e.set(i),e.set(r,i.length),r=e,i=null}for(;r.length>=t.value;){let i=r.slice(t.value);t=e.next(r.slice(0,t.value)),r=i}r.length>0&&(i=r)}}close(){this.input&&this.input.return(null)}}class st extends ot{constructor(e){super(e),e.debug.log("M7sDemux","init")}destroy(){super.destroy(),this.player.debug.log("M7sDemux","destroy")}dispatch(e){const t=this.player,i=new DataView(e),o=i.getUint8(0),r=i.getUint32(1,!1),s=new ArrayBuffer(4),a=new Uint32Array(s);switch(o){case C:if(t._opt.hasAudio){const i=new Uint8Array(e,5);t.updateStats({abps:i.byteLength}),i.byteLength>0&&this._doDecode(i,o,r)}break;case R:if(t._opt.hasVideo)if(t._times.demuxStart||(t._times.demuxStart=Se()),i.byteLength>5){const s=new Uint8Array(e,5),n=s[0];if(this._isEnhancedH265Header(n))this._decodeEnhancedH265Video(s,r);else{const e=i.getUint8(5)>>4==1;t.updateStats({vbps:s.byteLength}),a[0]=s[4],a[1]=s[3],a[2]=s[2],a[3]=0;let n=a[0];this._doDecode(s,o,r,e,n)}}else this.player.debug.warn("M7sDemux","dispatch","dv byteLength is",i.byteLength)}}}class at{constructor(e){return new(at.getLoaderFactory(e._opt.demuxType))(e)}static getLoaderFactory(e){return e===c?st:e===d?rt:void 0}}class nt{constructor(e){this.TAG="ExpGolomb",this._buffer=e,this._buffer_index=0,this._total_bytes=e.byteLength,this._total_bits=8*e.byteLength,this._current_word=0,this._current_word_bits_left=0}destroy(){this._buffer=null}_fillCurrentWord(){let e=this._total_bytes-this._buffer_index,t=Math.min(4,e),i=new Uint8Array(4);i.set(this._buffer.subarray(this._buffer_index,this._buffer_index+t)),this._current_word=new DataView(i.buffer).getUint32(0,!1),this._buffer_index+=t,this._current_word_bits_left=8*t}readBits(e){if(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 o=Math.min(i,this._current_word_bits_left),r=this._current_word>>>32-o;return this._current_word<<=o,this._current_word_bits_left-=o,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)}}class At{static _ebsp2rbsp(e){let t=e,i=t.byteLength,o=new Uint8Array(i),r=0;for(let e=0;e=2&&3===t[e]&&0===t[e-1]&&0===t[e-2]||(o[r]=t[e],r++);return new Uint8Array(o.buffer,0,r)}static parseSPS(e){let t=At._ebsp2rbsp(e),i=new nt(t);i.readByte();let o=i.readByte();i.readByte();let r=i.readByte();i.readUEG();let s=At.getProfileString(o),a=At.getLevelString(r),n=1,A=420,d=[0,420,422,444],c=8;if((100===o||110===o||122===o||244===o||44===o||83===o||86===o||118===o||128===o||138===o||144===o)&&(n=i.readUEG(),3===n&&i.readBits(1),n<=3&&(A=d[n]),c=i.readUEG()+8,i.readUEG(),i.readBits(1),i.readBool())){let e=3!==n?8:12;for(let t=0;t0&&e<16?(v=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2][e-1],w=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1][e-1]):255===e&&(v=i.readByte()<<8|i.readByte(),w=i.readByte()<<8|i.readByte())}if(i.readBool()&&i.readBool(),i.readBool()&&(i.readBits(4),i.readBool()&&i.readBits(24)),i.readBool()&&(i.readUEG(),i.readUEG()),i.readBool()){let e=i.readBits(32),t=i.readBits(32);E=i.readBool(),B=t,C=2*e,S=B/C}}let R=1;1===v&&1===w||(R=v/w);let k=0,T=0;if(0===n)k=1,T=2-g;else{k=3===n?1:2,T=(1===n?2:1)*(2-g)}let I=16*(h+1),x=16*(p+1)*(2-g);I-=(m+f)*k,x-=(b+y)*T;let D=Math.ceil(I*R);return i.destroy(),i=null,{profile_string:s,level_string:a,bit_depth:c,ref_frames:u,chroma_format:A,chroma_format_string:At.getChromaFormatString(A),frame_rate:{fixed:E,fps:S,fps_den:C,fps_num:B},sar_ratio:{width:v,height:w},codec_size:{width:I,height:x},present_size:{width:D,height:x}}}static _skipScalingList(e,t){let i=8,o=8,r=0;for(let s=0;s ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),void this.player.emit(O.webcodecsWidthOrHeightChange)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){const o=new EncodedVideoChunk({data:e.slice(5),timestamp:t,type:i?_:$});this.player.emit(F.timeUpdate,t);try{if(this.isDecodeStateClosed())return void this.player.debug.warn("Webcodecs","VideoDecoder isDecodeStateClosed true");this.decoder.decode(o)}catch(e){this.player.debug.error("Webcodecs","VideoDecoder",e),(-1!==e.toString().indexOf(Ae)||-1!==e.toString().indexOf(de))&&this.player.emitError(O.webcodecsDecodeError)}}else this.player.debug.warn("Webcodecs","VideoDecoder isDecodeFirstIIframe false")}else if(i&&0===e[1]){const t=15&e[0];if(this.player.video.updateVideoInfo({encTypeCode:t}),t===P)return void this.emit(O.webcodecsH265NotSupport);this.player._times.decodeStart||(this.player._times.decodeStart=Se());const i=function(e){let t=e.subarray(1,4),i="avc1.";for(let e=0;e<3;e++){let o=t[e].toString(16);o.length<2&&(o="0"+o),i+=o}return{codec:i,description:e}}(e.slice(5));this.player.debug.log("Webcodecs","VideoDecoder configure",i);try{this.decoder.configure(i)}catch(e){return this.player.debug.error("Webcodecs","VideoDecoder configure",e),void this.player.emit(O.webcodecsConfigureError)}this.hasInit=!0}}isDecodeStateClosed(){return"closed"===this.decoder.state}}const lt={play:"播放",pause:"暂停",audio:"",mute:"",screenshot:"截图",loading:"加载",fullscreen:"全屏",fullscreenExit:"退出全屏",record:"录制",recordStop:"停止录制"};var ut=Object.keys(lt).reduce((e,t)=>(e[t]=`\n \n ${lt[t]?`${lt[t]}`:""}\n`,e),{}),ht=(e,t)=>{const{events:{proxy:i}}=e,o=document.createElement("object");o.setAttribute("aria-hidden","true"),o.setAttribute("tabindex",-1),o.type="text/html",o.data="about:blank",Be(o,{display:"block",position:"absolute",top:"0",left:"0",height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"-1"});let r=e.width,s=e.height;i(o,"load",()=>{i(o.contentDocument.defaultView,"resize",()=>{e.width===r&&e.height===s||(r=e.width,s=e.height,e.emit(F.resize),n())})}),e.$container.appendChild(o),e.on(F.destroy,()=>{e.$container.removeChild(o)}),e.on(F.volumechange,()=>{!function(e){if(0===e)Be(t.$volumeOn,"display","none"),Be(t.$volumeOff,"display","flex"),Be(t.$volumeHandle,"top","48px");else if(t.$volumeHandle&&t.$volumePanel){const i=Ce(t.$volumePanel,"height")||60,o=Ce(t.$volumeHandle,"height"),r=i-(i-o)*e-o;Be(t.$volumeHandle,"top",`${r}px`),Be(t.$volumeOn,"display","flex"),Be(t.$volumeOff,"display","none")}t.$volumePanelText&&(t.$volumePanelText.innerHTML=parseInt(100*e))}(e.volume)}),e.on(F.loading,e=>{Be(t.$loading,"display",e?"flex":"none"),Be(t.$poster,"display","none"),e&&Be(t.$playBig,"display","none")});const a=i=>{let o=xe(i)?i:e.fullscreen;Be(t.$fullscreenExit,"display",o?"flex":"none"),Be(t.$fullscreen,"display",o?"none":"flex")},n=()=>{Te()&&t.$controls&&e._opt.useWebFullScreen&&setTimeout(()=>{if(e.fullscreen){let i=e.height/2-e.width+19,o=e.height/2-19;t.$controls.style.transform=`translateX(${-i}px) translateY(-${o}px) rotate(-90deg)`}else t.$controls.style.transform="translateX(0) translateY(0) rotate(0)"},10)};try{ye.on("change",a),e.events.destroys.push(()=>{ye.off("change",a)})}catch(e){}e.on(F.webFullscreen,e=>{a(e),n()}),e.on(F.recording,()=>{Be(t.$record,"display",e.recording?"none":"flex"),Be(t.$recordStop,"display",e.recording?"flex":"none"),Be(t.$recording,"display",e.recording?"flex":"none"),!e.recording&&t.$recordingTime&&(t.$recordingTime.innerHTML=je(0))}),e.on(F.recordingTimestamp,e=>{t.$recordingTime&&(t.$recordingTime.innerHTML=je(e))}),e.on(F.playing,e=>{Be(t.$play,"display",e?"none":"flex"),Be(t.$playBig,"display",e?"none":"block"),Be(t.$pause,"display",e?"flex":"none"),Be(t.$screenshot,"display",e?"flex":"none"),Be(t.$record,"display",e?"flex":"none"),Be(t.$qualityMenu,"display",e?"flex":"none"),Be(t.$volume,"display",e?"flex":"none"),a(),e||t.$speed&&(t.$speed.innerHTML=function(e){if(null==e||""===e||0===parseInt(e)||isNaN(parseInt(e)))return"0KB/s";let t=parseFloat(e);return t=t.toFixed(2),t+"KB/s"}(""))}),e.on(F.kBps,e=>{const i=function(e){if(null==e||""===e||0===parseFloat(e)||"NaN"===e)return"0 KB/s";const t=["B/s","KB/s","MB/s","GB/s","TB/s","PB/s","EB/s","ZB/s","YB/s"];let i=0;const o=parseFloat(e/8);i=Math.floor(Math.log(o)/Math.log(1024));let r=o/Math.pow(1024,i);return r=r.toFixed(2),r+(t[i]||t[0])}(1e3*e);t.$speed&&(t.$speed.innerHTML=i)})};function pt(e,t){void 0===t&&(t={});var i=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===i&&o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}pt('@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHHklEQVRoQ91bfYwdVRX/nTvbPuuqlEQM0q4IRYMSP0KkaNTEEAokNUEDFr9iEIOiuCC2++4dl+Tti9nOmbfWFgryESPhH7V+IIpG8SN+Fr8qqKgQEKoUkQREwXTLs8495mze1tf35s2bfTu7ndf758y55/x+c879OvcMYYnbxMTEy4IgOImIxkRkrYisNsasUrPe+wNE9C8ielRE9iVJsndmZubBpYRES6E8DMNXeu83ENHrAJwO4OUARvrY+i+ABwDcLSJ7jDF3RlF0f9H4CiNcrVZPCIJgk4hcCOCNBQH9EYBveO93NRqNx4rQuWjCExMT64IguEJE3kdEq4sA1alDRDTsb02SZOfMzMxDi7ExMGFr7THGGCciVwKYG5PL0HTMb69UKtNTU1Ozg9gbiLC1diMRXQ/gxEGMFtDnQRHZHMfxHQvVtWDCzrkdANSredvfRWQ3Ee0F8DCAJwDs994nQRCM6qxNROu892uI6A0ATs2rWER2xHF8VV55lctN2Dl3LICvA3hzDgMPENFXROT2SqVyb71efzZHnzkRnRNGRkY2isj5AM7K0e/HAN7OzP/MIZuP8OTk5FiSJDpjnpylVER+YIzZEUXRN/MY7ydTrVbXE9FlRPT+LFkiesh7f1Ycx4/009nXw9balxDRLwC8OEPZ/SLi4jjWCCi8WWtfA2CKiN6WofzxIAhePz09/dfMj5P1slqtPj8IgntEZF0vORH51Ozs7NU7d+5sFs60Q2EYhpeKyDUZq8LDInJ6HMdP98KS6WHn3E8BvKlHZx2X72Xmry410Xb91trTiOjLAF7Rw+5uZu6FufcYds7pl7wiTSkRPSUi5zHzr5eT7LytWq32gmaz+a0MZ1zDzB9LxZ72sFqtbjDGfLcHmWeI6IwoinTfe8RarVYzzWbzJxnb2A3M/P1OgF0hPT4+XhkdHd0H4LgUNv8xxpy5devW3x4xpm2Gt2zZMjoyMnJ363DSCemJ/fv3j3XOLV2EnXMNXQ57hPIFURTdVgay8xhaq4geKVem4Jph5mr788MIV6vVtcYY9W5XI6Iboij6SJnIzmNxzl0E4Itp2IIgWDs9Pf23+XeHEQ7D8EYR+VBKx8eYeU0ZybaR1s3OxhSMNzLzh7sIb968+YUrVqxQ7z6na6ATlS6UOzG2Qlv366bj3bMHDx4c27Zt25P6/JCHnXO6Cf90yhe6l5lfXWbvto3nm4no0hSHXRVFkR56/k/YWvsbItJ0zGFNRC6K4/hLQ0JYt8FdW0si2hNF0RmHCLcSbWnr6pPM/CIAMgyEFaNz7tsAzuvEmyTJKZotmQtpa+04EV2bQuo6Zh4fFrItwu8C8PmUSP1oHMfXzxEOw3CXiGzqFPLen9NoNL43TIQ19UREmmRY0YF7FzO/k5xzLwWgYdCZaZj13h/faDT+PUyEW15OO/T8MQiCjUr4HAC6Ee/MG/+MmfNkN0r3Pay124jo4x3ADuiBRwl/EMBNKTF/SxzHl5SOTQ5AzrnLANyQsjxdooRrmk1I0TPFzPUc+ksnYq09l4i+k8aJrLXbiajr7EhEV0ZRlDZzl45gJyDNhRljfpkCdLt6WF2vIdDZPsDMnys9uxSA1tpXEdHvU1599qgknHHqu/moDOlWNkTTyu2rTGKMOfeonLQ0lFunv08AOBPAXu/9jkajsafnsgTgVma+eBjHcBbmrI3HXcxc1D1vab5b1tbyQKVSOb5erz9TGrQFAMk8POhWLI7jOwuwUxoV/Y6Hn2Hmy0uDtgAgc4RbZQt/Ttl7PrVy5crj6vW6L8BWKVS057TuAqAX0p3t3cz8hVKgLQDEIcLW2suJ6LoUnX9i5tMKsFUKFYcIZ6VpAWxiZr2xG/p2WCI+4yDxeKVSWXM0jOXDCE9OTq5JkuTRNDcS0U1RFKWdqobK612XaWEYflJEru7BYuhDu4tw66ShxSFpd0laD7meme8ZKre2gU0teXDOnQ2gV3q2FBfig37wnjUevVI/auhIlzwMSnYOe1bnPkUtWrXznuUualkM2b6EtWzJGKMlBaf0MrScZUuLJduXsAq07l1/DuCEDIP3iUi4VIVpRRCd19G3Ek8FtfTQe//DrAI1lSu69LBIogsirMK1Wm11s9n8GoC35AByH4DbvPe3r1q16g8LKS7NoXtRIrk83G4ha/bugURL93cD+Mt8+TAR6YT3j0ql8rtBC70HZb1gwmooDMO3eu+vJaKTBjXc6rfPe39ho9H41SL15O4+EOFWiGv5n2sViz83t8VuwWW9pRyY8Dxu59zJIqJVAhcP+JPHI8y8bL8SLJrwPHH9jYeI3kFEF+Ssmp/rqjN7HMe6lV2WVhjhdrRhGJ7a+lFrPYDXAtB667Q/X5723p+tNwLLwrbf1rIIEBryxpgTkyQZA6DlFccS0fMA6G84d6RVvBZht5eO/wEB1Kvsoc6vtAAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAHn0lEQVRoQ81ZbYxcVRl+nnvu7ErSEmtqDdKwO3e2LWJLSEuFNiofFv9AUIpfiSFqCzt31lITGgEjHxKIKVirqXbnzpZSf5BAoHwIhpiAgDVSwBaU1rZLd+7skiIJKCWVpOzOPfc1d3dn986dO3Nn9kvuz3ve87zPc857znnPe4gZ+BZvlzPMed4XDG2sBGWFAGcRXET6ZwTwIsZpgbxL4B0ID/nKf8370Hz1xE08PV33nDKACDOO/roQ15K4TASfbQWLxL9E8AKJvcWs+WQrfcO2UxKQcfSNAn8TwKVTdVzdT/oJbi/aZl+reC0JsArelRDeC8jnW3XUnL0cofC2Ys58ojl7oDkBj4hKv697CXQnA8sxCEsE3hbKh4E9hfMEOBuUNMBzkzAE6Ct9SvXgW9RJtokC0r+VDqb8pyByfgOwZ0g84mv1cqmH/Y2cpntlmUG9BgauEcHVdW3JN6RsXF3axKFGeA0FdBVGVvpi/AnAJ2NAhkHpBU3H7eabSSMV1271yVL63g0C3gigPcbmA/r+umJP28F6+HUFZPLDy4XqVQCjW2HkexJQN7s2j0+FeLRPZqd0idL3Algfg/cRRa8u5toPx/mKFZDJyyKhPgZgQU0nssfNqvxMEK8RktdZoThxM2G0qaUDG/hetC1WgOXo1wG5IGJcNkS+OpBLvTgb5CuYXfnypT75x2hICfh6yVYrEwWknfJ9BH8cJU/fX9MoFmdS1Pja2w+gLYwrkF+U7NTN4X9VM9CxUz6nlD5So5JyeTGbemEmSSZhZQrly0T4fNROa3Xe0A95tPK/SoDleH8DcGF1J97q2ipYYHP+WY6+BZCtEccHXNtcXSPA6iuvg89nGxnPuQIAlqMPAhKJfVnn2qlge588iS3H2wfgS1XxJXpFve0rbNexS9JKwzQIvxmRvsDQCt7QDSwl2ad7h8+nof4Rsdvn2uYlEwKCAwW+jp6gT7u2Wf+kBBCcqjT8RwFZkUQktp18AzS+mXQQWo73NICrqjHU0uAcGl0DlqPvAOSusIFP/+LBbNsrjYhZjvccgK9MiXylk+A5N2de0QijszBykSHGy1XRQd5RzKq7RwVkHG+/ABdPGBADbtZckkTMcjw3mIgku0btArgl28wkYViONxBQndSN/SXbXMvRZM3UQS4zuedS7nOzqVuSQfXh6afW/Kdrq+VJvmLOpxFQLaHleEH+8VgE4ErXNp9JArUcfQiQROeNcXjYtVXiGhq7i+AP1ZsM1tNy9E8A+XmowfdFZQZzHPw4CejMS6dBHYRs6OzirbTyXi+IXIjsiXPeUekX76L3cRJw6Z1ivnWWDgb17BCvXloF7yEIvjP5k4dcWzW6vEyYzmUIje+W0ZB9KFgDjwO4JqTqFdc2J3ekBtMw9wK8YCu9KETpiWAG9kJwbejnQdc2I/lQvIr/g4ADAFaF2OwNZmAPgO9P/pQ3XTu1LCn+60xpM90iNs3tQmP+yv2RUs4eWk55K8Dwnn/Kb1cdgz/gB0ls5nIGzumVBaahgwv+/AleIluZcbxuAQpV+6vvX9jM5WUuBWR6R1aJYQQhFOKPbnY55TU++FL1aDPn2irublplNpcCrILOQaQ3TMCArGXnHvmEGtHFcG2TxFPFrPm15BAqHwPY1HqpjyX9rp1KLHbFZKRv++2qazwb9R4E8N2Qk7IxohYObOapRiLSjlckYCUJbdTeTDLXtUPO9Nv0fwCYIawHXdu8riIgJh/iFtdW2xsKKOgtFNk2HQEQ3uTm1K9a9UPB+qCGOipgVUFSJ0W/W1WBE7zn5sxFSeTSee86EpdT4ImBxFpmgEcfSgglwPMl2wxmv+FnOV5QD1oYMjq5gOozB7MsTyRGVkHfCZGfVe1G4O1FW92T5GA22+MuWwK5p2Snbh8djIrz83bKvI+Ufh9AKrxT+aKsZjLT2RAxdtfWxeoMFJ7frj5dOaeqyioZR98mkLurycgR107N0ntAUuiUj0bL8YxERU1p0Sp4gxB0VEETj7lZ8xuzMcr1MGNytCBehtys2Vkd5hGE8bJeXDl7t2ub18+FiEze2yVEjS+D/qqBbNtrDQUEjWNvYLIjSlaA36sR9e2BzRyeDSHBocph/TCBmkOU4OairX4T9Vv3fcByyr8G+KMaosSAaNlQ6kn9ZSZFWIXyFyH8XbjyUMEXkR2lXKqWS2R11/CxHO9+ABtjiQryMNRWN8u3piOka5cs9rX+KQA7Fod4wM2a8RySBIyGU768TcgtdUieJrEbvjxczKX+2oqQ8REPrrLfAzAvri8h24p2Klrqj+wvTXhNO95GjqXcqp45KUcF3CfAAaEcN+H/25e2/wb2BkfmezAWUrgEgtWEfDnhtVJD0O3mzAeS6CW+UlYArMLwCoj6JYCGZcCIw8pij3vAq8dtH6g3udn2Q0nkg/amBVTA0gXveopsaea9txkCkzZynOC2Vl/rWxYwMSN5b8PoAifWtkY0Yi14CcT9rm0Gd/OWvykLqHjq7Bu5QIm6QkQuAbG85hSPUiKGIDhM8s+a+tnB7ra/t8w61GHaAsLOl+2W+WVdPpfaWCzBE63BM0fbfTlF4KQo/0RKpY71b+To4p6J73/tXyc1fevA3AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:"";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}');class gt{constructor(e){var t;this.player=e,((e,t)=>{e._opt.hasControl&&e._opt.controlAutoHide?e.$container.classList.add("jessibuca-controls-show-auto-hide"):e.$container.classList.add("jessibuca-controls-show");const i=e._opt,o=i.operateBtns;e.$container.insertAdjacentHTML("beforeend",`\n ${i.background?`
`:""}\n
\n ${ut.loading}\n ${i.loadingText?`
${i.loadingText}
`:""}\n
\n ${i.hasControl&&o.play?'
':""}\n ${i.hasControl?`\n
\n
\n
00:00:01
\n
${ut.recordStop}
\n
\n `:""}\n ${i.hasControl?`\n
\n
\n
\n ${i.showBandwidth?'
':""}\n
\n
\n ${o.audio?`\n
\n ${ut.audio}\n ${ut.mute}\n
\n
\n
\n
\n
\n
\n
\n `:""}\n ${o.play?`
${ut.play}
${ut.pause}
`:""}\n ${o.screenshot?`
${ut.screenshot}
`:""}\n ${o.record?`
${ut.record}
${ut.recordStop}
`:""}\n ${o.fullscreen?`
${ut.fullscreen}
${ut.fullscreenExit}
`:""}\n
\n
\n
\n `:""}\n\n `),Object.defineProperty(t,"$poster",{value:e.$container.querySelector(".jessibuca-poster")}),Object.defineProperty(t,"$loading",{value:e.$container.querySelector(".jessibuca-loading")}),Object.defineProperty(t,"$play",{value:e.$container.querySelector(".jessibuca-play")}),Object.defineProperty(t,"$playBig",{value:e.$container.querySelector(".jessibuca-play-big")}),Object.defineProperty(t,"$recording",{value:e.$container.querySelector(".jessibuca-recording")}),Object.defineProperty(t,"$recordingTime",{value:e.$container.querySelector(".jessibuca-recording-time")}),Object.defineProperty(t,"$recordingStop",{value:e.$container.querySelector(".jessibuca-recording-stop")}),Object.defineProperty(t,"$pause",{value:e.$container.querySelector(".jessibuca-pause")}),Object.defineProperty(t,"$controls",{value:e.$container.querySelector(".jessibuca-controls")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$volume",{value:e.$container.querySelector(".jessibuca-volume")}),Object.defineProperty(t,"$volumePanelWrap",{value:e.$container.querySelector(".jessibuca-volume-panel-wrap")}),Object.defineProperty(t,"$volumePanelText",{value:e.$container.querySelector(".jessibuca-volume-panel-text")}),Object.defineProperty(t,"$volumePanel",{value:e.$container.querySelector(".jessibuca-volume-panel")}),Object.defineProperty(t,"$volumeHandle",{value:e.$container.querySelector(".jessibuca-volume-panel-handle")}),Object.defineProperty(t,"$volumeOn",{value:e.$container.querySelector(".jessibuca-icon-audio")}),Object.defineProperty(t,"$volumeOff",{value:e.$container.querySelector(".jessibuca-icon-mute")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreenExit",{value:e.$container.querySelector(".jessibuca-fullscreen-exit")}),Object.defineProperty(t,"$record",{value:e.$container.querySelector(".jessibuca-record")}),Object.defineProperty(t,"$recordStop",{value:e.$container.querySelector(".jessibuca-record-stop")}),Object.defineProperty(t,"$screenshot",{value:e.$container.querySelector(".jessibuca-screenshot")}),Object.defineProperty(t,"$speed",{value:e.$container.querySelector(".jessibuca-speed")})})(e,this),t=this,Object.defineProperty(t,"controlsRect",{get:()=>t.$controls.getBoundingClientRect()}),ht(e,this),((e,t)=>{const{events:{proxy:i},debug:o}=e;function r(e){const{bottom:i,height:o}=t.$volumePanel.getBoundingClientRect(),{height:r}=t.$volumeHandle.getBoundingClientRect();return Ee(i-e.y-r/2,0,o-r/2)/(o-r)}if(i(window,["click","contextmenu"],i=>{i.composedPath().indexOf(e.$container)>-1?t.isFocus=!0:t.isFocus=!1}),i(window,"orientationchange",()=>{setTimeout(()=>{e.resize()},300)}),i(t.$controls,"click",e=>{e.stopPropagation()}),i(t.$pause,"click",t=>{e.pause()}),i(t.$play,"click",t=>{e.play(),e.resumeAudioAfterPause()}),i(t.$playBig,"click",t=>{e.play(),e.resumeAudioAfterPause()}),i(t.$volume,"mouseover",()=>{t.$volumePanelWrap.classList.add("jessibuca-volume-panel-wrap-show")}),i(t.$volume,"mouseout",()=>{t.$volumePanelWrap.classList.remove("jessibuca-volume-panel-wrap-show")}),i(t.$volumeOn,"click",i=>{i.stopPropagation(),Be(t.$volumeOn,"display","none"),Be(t.$volumeOff,"display","block");const o=e.volume;e.volume=0,e._lastVolume=o}),i(t.$volumeOff,"click",i=>{i.stopPropagation(),Be(t.$volumeOn,"display","block"),Be(t.$volumeOff,"display","none"),e.volume=e.lastVolume||.5}),i(t.$screenshot,"click",t=>{t.stopPropagation(),e.video.screenshot()}),i(t.$volumePanel,"click",t=>{t.stopPropagation(),e.volume=r(t)}),i(t.$volumeHandle,"mousedown",()=>{t.isVolumeDroging=!0}),i(t.$volumeHandle,"mousemove",i=>{t.isVolumeDroging&&(e.volume=r(i))}),i(document,"mouseup",()=>{t.isVolumeDroging&&(t.isVolumeDroging=!1)}),i(t.$record,"click",t=>{t.stopPropagation(),e.recording=!0}),i(t.$recordStop,"click",t=>{t.stopPropagation(),e.recording=!1}),i(t.$recordingStop,"click",t=>{t.stopPropagation(),e.recording=!1}),i(t.$fullscreen,"click",t=>{t.stopPropagation(),e.fullscreen=!0}),i(t.$fullscreenExit,"click",t=>{t.stopPropagation(),e.fullscreen=!1}),e._opt.hasControl&&e._opt.controlAutoHide){i(e.$container,"mouseover",()=>{e.fullscreen||(Be(t.$controls,"display","block"),r())}),i(e.$container,"mousemove",()=>{e.$container&&t.$controls&&(e.fullscreen,"none"===t.$controls.style.display&&(Be(t.$controls,"display","block"),r()))}),i(e.$container,"mouseout",()=>{s(),Be(t.$controls,"display","none")});let o=null;const r=()=>{s(),o=setTimeout(()=>{Be(t.$controls,"display","none")},5e3)},s=()=>{o&&(clearTimeout(o),o=null)}}})(e,this),e._opt.hotKey&&((e,t)=>{const{events:{proxy:i}}=e,o={};function r(e,t){o[e]?o[e].push(t):o[e]=[t]}r(se,()=>{e.fullscreen&&(e.fullscreen=!1)}),r(ae,()=>{e.volume+=.05}),r(ne,()=>{e.volume-=.05}),i(window,"keydown",e=>{if(t.isFocus){const t=document.activeElement.tagName.toUpperCase(),i=document.activeElement.getAttribute("contenteditable");if("INPUT"!==t&&"TEXTAREA"!==t&&""!==i&&"true"!==i){const t=o[e.keyCode];t&&(e.preventDefault(),t.forEach(e=>e()))}}})})(e,this),this.player.debug.log("Control","init")}destroy(){if(this.$poster){if(!Fe(this.$poster)){const e=this.player.$container.querySelector(".jessibuca-poster");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$loading){if(!Fe(this.$loading)){const e=this.player.$container.querySelector(".jessibuca-loading");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$controls){if(!Fe(this.$controls)){const e=this.player.$container.querySelector(".jessibuca-controls");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$recording){if(!Fe(this.$recording)){const e=this.player.$container.querySelector(".jessibuca-recording");e&&this.player.$container&&this.player.$container.removeChild(e)}}if(this.$playBig){if(!Fe(this.$playBig)){const e=this.player.$container.querySelector(".jessibuca-play-big");e&&this.player.$container&&this.player.$container.removeChild(e)}}this.player.$container&&(this.player.$container.classList.remove("jessibuca-controls-show-auto-hide"),this.player.$container.classList.remove("jessibuca-controls-show")),this.player.debug.log("control","destroy")}autoSize(){const e=this.player;e.$container.style.padding="0 0";const t=e.width,i=e.height,o=t/i,r=e.video.$videoElement.width/e.video.$videoElement.height;if(o>r){const o=(t-i*r)/2;e.$container.style.padding=`0 ${o}px`}else{const o=(i-t/r)/2;e.$container.style.padding=`${o}px 0`}}toggleBar(e){this.$controls&&(xe(e)||(e="none"===Ce(this.$controls,"display",!1)),Be(this.$controls,"display",e?"flex":"none"))}getBarIsShow(){let e=!1;return this.$controls&&(e="none"!==Ce(this.$controls,"display",!1)),e}}pt(".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}");class mt{static init(){mt.types={avc1:[],avcC:[],hvc1:[],hvcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]};for(let e in mt.types)mt.types.hasOwnProperty(e)&&(mt.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);let e=mt.constants={};e.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),e.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),e.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSC=e.STCO=e.STTS,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),e.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),e.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])}static box(e){let t=8,i=null,o=Array.prototype.slice.call(arguments,1),r=o.length;for(let e=0;e>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);let s=8;for(let e=0;e>>24&255,e>>>16&255,e>>>8&255,255&e,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))}static trak(e){return mt.box(mt.types.trak,mt.tkhd(e),mt.mdia(e))}static tkhd(e){let t=e.id,i=e.duration,o=e.presentWidth,r=e.presentHeight;return mt.box(mt.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,o>>>8&255,255&o,0,0,r>>>8&255,255&r,0,0]))}static mdia(e){return mt.box(mt.types.mdia,mt.mdhd(e),mt.hdlr(e),mt.minf(e))}static mdhd(e){let t=e.timescale,i=e.duration;return mt.box(mt.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,85,196,0,0]))}static hdlr(e){let t=null;return t="audio"===e.type?mt.constants.HDLR_AUDIO:mt.constants.HDLR_VIDEO,mt.box(mt.types.hdlr,t)}static minf(e){let t=null;return t="audio"===e.type?mt.box(mt.types.smhd,mt.constants.SMHD):mt.box(mt.types.vmhd,mt.constants.VMHD),mt.box(mt.types.minf,t,mt.dinf(),mt.stbl(e))}static dinf(){return mt.box(mt.types.dinf,mt.box(mt.types.dref,mt.constants.DREF))}static stbl(e){return mt.box(mt.types.stbl,mt.stsd(e),mt.box(mt.types.stts,mt.constants.STTS),mt.box(mt.types.stsc,mt.constants.STSC),mt.box(mt.types.stsz,mt.constants.STSZ),mt.box(mt.types.stco,mt.constants.STCO))}static stsd(e){return"audio"===e.type?mt.box(mt.types.stsd,mt.constants.STSD_PREFIX,mt.mp4a(e)):"avc"===e.videoType?mt.box(mt.types.stsd,mt.constants.STSD_PREFIX,mt.avc1(e)):mt.box(mt.types.stsd,mt.constants.STSD_PREFIX,mt.hvc1(e))}static mp4a(e){let t=e.channelCount,i=e.audioSampleRate,o=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t,0,16,0,0,0,0,i>>>8&255,255&i,0,0]);return mt.box(mt.types.mp4a,o,mt.esds(e))}static esds(e){let t=e.config||[],i=t.length,o=new Uint8Array([0,0,0,0,3,23+i,0,1,0,4,15+i,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([i]).concat(t).concat([6,1,2]));return mt.box(mt.types.esds,o)}static avc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return mt.box(mt.types.avc1,r,mt.box(mt.types.avcC,t))}static hvc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return mt.box(mt.types.hvc1,r,mt.box(mt.types.hvcC,t))}static mvex(e){return mt.box(mt.types.mvex,mt.trex(e))}static trex(e){let t=e.id,i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return mt.box(mt.types.trex,i)}static moof(e,t){return mt.box(mt.types.moof,mt.mfhd(e.sequenceNumber),mt.traf(e,t))}static mfhd(e){let t=new Uint8Array([0,0,0,0,e>>>24&255,e>>>16&255,e>>>8&255,255&e]);return mt.box(mt.types.mfhd,t)}static traf(e,t){let i=e.id,o=mt.box(mt.types.tfhd,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),r=mt.box(mt.types.tfdt,new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t])),s=mt.sdtp(e),a=mt.trun(e,s.byteLength+16+16+8+16+8+8);return mt.box(mt.types.traf,o,r,a,s)}static sdtp(e){let t=new Uint8Array(5),i=e.flags;return t[4]=i.isLeading<<6|i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy,mt.box(mt.types.sdtp,t)}static trun(e,t){let i=new Uint8Array(28);t+=36,i.set([0,0,15,1,0,0,0,1,t>>>24&255,t>>>16&255,t>>>8&255,255&t],0);let o=e.duration,r=e.size,s=e.flags,a=e.cts;return i.set([o>>>24&255,o>>>16&255,o>>>8&255,255&o,r>>>24&255,r>>>16&255,r>>>8&255,255&r,s.isLeading<<2|s.dependsOn,s.isDependedOn<<6|s.hasRedundancy<<4|s.isNonSync,0,0,a>>>24&255,a>>>16&255,a>>>8&255,255&a],12),mt.box(mt.types.trun,i)}static mdat(e){return mt.box(mt.types.mdat,e)}}mt.init();class ft extends Ve{constructor(e){super(),this.player=e,this.isAvc=!0,this.mediaSource=new window.MediaSource,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.cacheTrack={},this.timeInit=!1,this.sequenceNumber=0,this.mediaSourceOpen=!1,this.dropping=!1,this.firstRenderTime=null,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.player.video.$videoElement.src=window.URL.createObjectURL(this.mediaSource);const{debug:t,events:{proxy:i}}=e;i(this.mediaSource,"sourceopen",()=>{this.mediaSourceOpen=!0,this.player.emit(F.mseSourceOpen)}),i(this.mediaSource,"sourceclose",()=>{this.player.emit(F.mseSourceClose)}),e.debug.log("MediaSource","init")}destroy(){this.stop(),this.mediaSource=null,this.mediaSourceOpen=!1,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.off(),this.player.debug.log("MediaSource","destroy")}get state(){return this.mediaSource&&this.mediaSource.readyState}get isStateOpen(){return this.state===ie}get isStateClosed(){return this.state===oe}get isStateEnded(){return this.state===te}get duration(){return this.mediaSource&&this.mediaSource.duration}set duration(e){this.mediaSource.duration=e}decodeVideo(e,t,i,o){const r=this.player;if(!(!r||r&&r.isDestroyedOrClosed()))if(this.hasInit){if(i&&0===e[1]){let t=dt(e.slice(5));const i=this.player.video.videoInfo;i&&i.width&&i.height&&t&&t.codecWidth&&t.codecHeight&&(t.codecWidth!==i.width||t.codecHeight!==i.height)&&(this.player.debug.warn("MediaSource",`width or height is update, width ${i.width}-> ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),this.isInitInfo=!1,this.player.video.init=!1)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){null===this.firstRenderTime&&(this.firstRenderTime=t);const r=t-this.firstRenderTime;this._decodeVideo(e,r,i,o)}else this.player.debug.warn("MediaSource","decodeVideo isDecodeFirstIIframe false")}else if(i&&0===e[1]){const o=15&e[0];if(r.video.updateVideoInfo({encTypeCode:o}),o===P)return void this.emit(O.mediaSourceH265NotSupport);r._times.decodeStart||(r._times.decodeStart=Se()),this._decodeConfigurationRecord(e,t,i,o),this.hasInit=!0}}_decodeConfigurationRecord(e,t,i,o){let r=e.slice(5),s={};s=dt(r);const a={id:1,type:"video",timescale:1e3,duration:0,avcc:r,codecWidth:s.codecWidth,codecHeight:s.codecHeight,videoType:s.videoType},n=mt.generateInitSegment(a);this.isAvc=!0,this.appendBuffer(n.buffer),this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1}_decodeVideo(e,t,i,o){const r=this.player;let s=e.slice(5),a=s.byteLength;const n=r.video.$videoElement,A=r._opt.videoBufferDelay;if(n.buffered.length>1&&(this.removeBuffer(n.buffered.start(0),n.buffered.end(0)),this.timeInit=!1),this.dropping&&t-this.cacheTrack.dts>A)this.dropping=!1,this.cacheTrack={};else if(this.cacheTrack&&t>=this.cacheTrack.dts){let e=8+this.cacheTrack.size,i=new Uint8Array(e);i[0]=e>>>24&255,i[1]=e>>>16&255,i[2]=e>>>8&255,i[3]=255&e,i.set(mt.types.mdat,4),i.set(this.cacheTrack.data,8),this.cacheTrack.duration=t-this.cacheTrack.dts;let o=mt.moof(this.cacheTrack,this.cacheTrack.dts),s=new Uint8Array(o.byteLength+i.byteLength);s.set(o,0),s.set(i,o.byteLength),this.appendBuffer(s.buffer),r.handleRender(),r.updateStats({fps:!0,ts:t,buf:r.demux&&r.demux.delay||0}),r._times.videoStart||(r._times.videoStart=Se(),r.handlePlayToRenderTimes())}else r.debug.log("MediaSource","timeInit set false , cacheTrack = {}"),this.timeInit=!1,this.cacheTrack={};this.cacheTrack||(this.cacheTrack={}),this.cacheTrack.id=1,this.cacheTrack.sequenceNumber=++this.sequenceNumber,this.cacheTrack.size=a,this.cacheTrack.dts=t,this.cacheTrack.cts=o,this.cacheTrack.isKeyframe=i,this.cacheTrack.data=s,this.cacheTrack.flags={isLeading:0,dependsOn:i?2:1,isDependedOn:i?1:0,hasRedundancy:0,isNonSync:i?0:1},this.timeInit||1!==n.buffered.length||(r.debug.log("MediaSource","timeInit set true"),this.timeInit=!0,n.currentTime=n.buffered.end(0)),!this.isInitInfo&&n.videoWidth>0&&n.videoHeight>0&&(r.debug.log("MediaSource",`updateVideoInfo: ${n.videoWidth},${n.videoHeight}`),r.video.updateVideoInfo({width:n.videoWidth,height:n.videoHeight}),r.video.initCanvasViewSize(),this.isInitInfo=!0)}appendBuffer(e){const{debug:t,events:{proxy:i}}=this.player;if(null===this.sourceBuffer&&(this.sourceBuffer=this.mediaSource.addSourceBuffer(ee),i(this.sourceBuffer,"error",e=>{t.error("MediaSource","sourceBuffer error",e),this.player.emit(F.mseSourceBufferError,e)})),this.mediaSourceAppendBufferError)t.error("MediaSource","this.mediaSourceAppendBufferError is true");else if(this.mediaSourceAppendBufferFull)t.error("MediaSource","this.mediaSourceAppendBufferFull is true");else if(!1===this.sourceBuffer.updating&&this.isStateOpen)try{this.sourceBuffer.appendBuffer(e)}catch(e){t.warn("MediaSource","this.sourceBuffer.appendBuffer()",e.code,e),22===e.code?(this.stop(),this.mediaSourceAppendBufferFull=!0,this.emit(O.mediaSourceFull)):11===e.code?(this.stop(),this.mediaSourceAppendBufferError=!0,this.emit(O.mediaSourceAppendBufferError)):(t.error("MediaSource","appendBuffer error",e),this.player.emit(F.mseSourceBufferError,e))}else this.isStateClosed?this.player.emitError(O.mseSourceBufferError,"mediaSource is not attached to video or mediaSource is closed"):this.isStateEnded?this.player.emitError(O.mseSourceBufferError,"mediaSource is closed"):!0===this.sourceBuffer.updating&&this.player.emit(F.mseSourceBufferBusy)}stop(){this.abortSourceBuffer(),this.removeSourceBuffer(),this.endOfStream()}dropSourceBuffer(e){const t=this.player.video.$videoElement;this.dropping=e,t.buffered.length>0&&t.buffered.end(0)-t.currentTime>1&&(this.player.debug.warn("MediaSource","dropSourceBuffer",`$video.buffered.end(0) is ${t.buffered.end(0)} - $video.currentTime ${t.currentTime}`),t.currentTime=t.buffered.end(0))}removeBuffer(e,t){if(this.isStateOpen&&!1===this.sourceBuffer.updating)try{this.sourceBuffer.remove(e,t)}catch(e){this.player.debug.warn("MediaSource","removeBuffer() error",e)}else this.player.debug.warn("MediaSource","removeBuffer() this.isStateOpen is",this.isStateOpen,"this.sourceBuffer.updating",this.sourceBuffer.updating)}endOfStream(){const e=this.player.video&&this.player.video.$videoElement;if(this.isStateOpen&&e&&e.readyState>=1)try{this.mediaSource.endOfStream()}catch(e){this.player.debug.warn("MediaSource","endOfStream() error",e)}}abortSourceBuffer(){this.isStateOpen&&this.sourceBuffer&&(this.sourceBuffer.abort(),this.sourceBuffer=null)}removeSourceBuffer(){if(!this.isStateClosed&&this.mediaSource&&this.sourceBuffer)try{this.mediaSource.removeSourceBuffer(this.sourceBuffer)}catch(e){this.player.debug.warn("MediaSource","removeSourceBuffer() error",e)}}getSourceBufferUpdating(){return this.sourceBuffer&&this.sourceBuffer.updating}}const bt=()=>"undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,yt=()=>"wakeLock"in navigator;class vt{constructor(e){if(this.player=e,this.enabled=!1,yt()){this._wakeLock=null;const e=()=>{null!==this._wakeLock&&"visible"===document.visibilityState&&this.enable()};document.addEventListener("visibilitychange",e),document.addEventListener("fullscreenchange",e)}else bt()?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("title","No Sleep"),this.noSleepVideo.setAttribute("playsinline",""),this._addSourceToVideo(this.noSleepVideo,"webm","data:video/webm;base64,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"),this._addSourceToVideo(this.noSleepVideo,"mp4","data:video/mp4;base64,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"),this.noSleepVideo.addEventListener("loadedmetadata",()=>{this.noSleepVideo.duration<=1?this.noSleepVideo.setAttribute("loop",""):this.noSleepVideo.addEventListener("timeupdate",()=>{this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())})}))}_addSourceToVideo(e,t,i){var o=document.createElement("source");o.src=i,o.type=`video/${t}`,e.appendChild(o)}get isEnabled(){return this.enabled}enable(){const e=this.player.debug;if(yt())return navigator.wakeLock.request("screen").then(t=>{this._wakeLock=t,this.enabled=!0,e.log("wakeLock","Wake Lock active."),this._wakeLock.addEventListener("release",()=>{e.log("wakeLock","Wake Lock released.")})}).catch(t=>{throw this.enabled=!1,e.error("wakeLock",`${t.name}, ${t.message}`),t});if(bt())return this.disable(),this.noSleepTimer=window.setInterval(()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))},15e3),this.enabled=!0,Promise.resolve();return this.noSleepVideo.play().then(e=>(this.enabled=!0,e)).catch(e=>{throw this.enabled=!1,e})}disable(){const e=this.player.debug;yt()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):bt()?this.noSleepTimer&&(e.warn("wakeLock","NoSleep now disabled for older iOS devices."),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}class wt extends Ve{constructor(e,t){var i;super(),this.$container=e,this._opt=Object.assign({},g,t),this.debug=new fe(this),this.debug.log("Player","init"),this._opt.forceNoOffscreen=!0,(Te()||/ipad|android(?!.*mobile)|tablet|kindle|silk/i.test(window.navigator.userAgent.toLowerCase()))&&(this.debug.log("Player","isMobile and set _opt.controlAutoHide false"),this._opt.controlAutoHide=!1),this._opt.autoUseSystemFullScreen&&(ye.isEnabled&&this._opt.useWebFullScreen&&(this.debug.log("Player","screenfull.isEnabled is true and _opt.useWebFullScreen is true , set _opt.useWebFullScreen false"),this._opt.useWebFullScreen=!1),Oe(ye.isEnabled)&&Oe(this._opt.useWebFullScreen)&&(this.debug.log("Player","screenfull.isEnabled is false and _opt.useWebFullScreen is false , set _opt.useWebFullScreen true"),this._opt.useWebFullScreen=!0)),this._opt.useWCS&&(this._opt.useWCS="VideoEncoder"in window),this._opt.useMSE&&(this._opt.useMSE=window.MediaSource&&window.MediaSource.isTypeSupported(ee)),this._opt.wcsUseVideoRender&&(this._opt.wcsUseVideoRender=window.MediaStreamTrackGenerator&&"function"==typeof window.MediaStreamTrackGenerator),this._opt.useMSE&&(this._opt.useWCS&&this.debug.log("Player","useWCS set true->false"),this._opt.forceNoOffscreen||this.debug.log("Player","forceNoOffscreen set false->true"),this._opt.useWCS=!1,this._opt.forceNoOffscreen=!0),this._opt.forceNoOffscreen||("undefined"==typeof OffscreenCanvas?(this._opt.forceNoOffscreen=!0,this._opt.useOffscreen=!1):this._opt.useOffscreen=!0),this._opt.hasAudio||(this._opt.operateBtns.audio=!1),this._opt.hasControl=this._hasControl(),this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._destroyed=!1,this._closed=!1,this._checkHeartTimeout=null,this._checkLoadingTimeout=null,this._checkStatsInterval=null,this._startBpsTime=null,this._isPlayingBeforePageHidden=!1,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0},this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this._videoTimestamp=0,this._audioTimestamp=0,i=this,Object.defineProperty(i,"rect",{get:()=>{const e=i.$container.getBoundingClientRect();return e.width=Math.max(e.width,i.$container.clientWidth),e.height=Math.max(e.height,i.$container.clientHeight),e}}),["bottom","height","left","right","top","width"].forEach(e=>{Object.defineProperty(i,e,{get:()=>i.rect[e]})}),this.events=new be(this),this.video=new Ye(this),this._opt.hasAudio&&(this.audio=new qe(this)),this.recorder=new tt(this),this._onlyMseOrWcsVideo()?this.loaded=!0:this.decoderWorker=new it(this),this.stream=null,this.demux=null,this._lastVolume=null,this._opt.useWCS&&(this.webcodecsDecoder=new ct(this),this.loaded=!0),this._opt.useMSE&&(this.mseDecoder=new ft(this),this.loaded=!0),this.control=new gt(this),Te()&&(this.keepScreenOn=new vt(this)),(e=>{try{const t=t=>{Le(t)===e.$container&&(e.emit(M.fullscreen,e.fullscreen),e.fullscreen?e._opt.useMSE&&e.resize():e.resize())};ye.on("change",t),e.events.destroys.push(()=>{ye.off("change",t)})}catch(e){}if(e.on(F.decoderWorkerInit,()=>{e.debug.log("player","has loaded"),e.loaded=!0}),e.on(F.play,()=>{e.loading=!1}),e.on(F.fullscreen,t=>{if(t)try{ye.request(e.$container).then(()=>{}).catch(t=>{Te()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)})}catch(t){Te()&&e._opt.useWebFullScreen&&(e.webFullscreen=!0)}else try{ye.exit().then(()=>{e.webFullscreen&&(e.webFullscreen=!1)}).catch(()=>{e.webFullscreen=!1})}catch(t){e.webFullscreen=!1}}),Te()&&e.on(F.webFullscreen,t=>{t?e.$container.classList.add("jessibuca-fullscreen-web"):e.$container.classList.remove("jessibuca-fullscreen-web"),e.emit(M.fullscreen,e.fullscreen)}),e.on(F.resize,()=>{e.video&&e.video.resize()}),e._opt.debug){const t=[F.timeUpdate],i=[F.stats,F.playToRenderTimes,F.audioInfo,F.videoInfo];Object.keys(F).forEach(o=>{e.on(F[o],r=>{t.includes(o)||(i.includes(o)&&(r=JSON.stringify(r)),e.debug.log("player events",F[o],r))})}),Object.keys(O).forEach(t=>{e.on(O[t],i=>{e.debug.log("player event error",O[t],i)})})}})(this),(e=>{const{_opt:t,debug:i,events:{proxy:o}}=e;t.supportDblclickFullscreen&&o(e.$container,"dblclick",t=>{const i=Le(t).nodeName.toLowerCase();"canvas"!==i&&"video"!==i||(e.fullscreen=!e.fullscreen)}),o(document,"visibilitychange",()=>{t.hiddenAutoPause&&(i.log("visibilitychange",document.visibilityState,e._isPlayingBeforePageHidden),"visible"===document.visibilityState?e._isPlayingBeforePageHidden&&e.play():(e._isPlayingBeforePageHidden=e.playing,e.playing&&e.pause()))}),o(window,"fullscreenchange",()=>{null!==e.keepScreenOn&&"visible"===document.visibilityState&&e.enableWakeLock()})})(this),this.debug.log("Player","init and version is",p),this._opt.useWCS&&this.debug.log("Player","use WCS"),this._opt.useMSE&&this.debug.log("Player","use MSE"),this._opt.useOffscreen&&this.debug.log("Player","use offscreen");try{this.debug.log("Player options",JSON.stringify(this._opt))}catch(e){}}async destroy(){this._destroyed=!0,this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._lastVolume=null,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this.decoderWorker&&(await this.decoderWorker.destroy(),this.decoderWorker=null),this.video&&(this.video.destroy(),this.video=null),this.audio&&(this.audio.destroy(),this.audio=null),this.stream&&(await this.stream.destroy(),this.stream=null),this.recorder&&(this.recorder.destroy(),this.recorder=null),this.control&&(this.control.destroy(),this.control=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.demux&&(this.demux.destroy(),this.demux=null),this.events&&(this.events.destroy(),this.events=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.releaseWakeLock(),this.keepScreenOn=null,this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this.emit("destroy"),this.off(),this.debug.log("play","destroy end")}set fullscreen(e){Te()&&this._opt.useWebFullScreen?(this.emit(F.webFullscreen,e),setTimeout(()=>{this.updateOption({rotate:e?270:0}),this.resize()},10)):this.emit(F.fullscreen,e)}get fullscreen(){return ye.isFullscreen||this.webFullscreen}set webFullscreen(e){this.emit(F.webFullscreen,e)}get webFullscreen(){return this.$container.classList.contains("jessibuca-fullscreen-web")}set loaded(e){this._hasLoaded=e}get loaded(){return this._hasLoaded}set playing(e){e&&(this.loading=!1),this.playing!==e&&(this._playing=e,this.emit(F.playing,e),this.emit(F.volumechange,this.volume),e?this.emit(F.play):this.emit(F.pause))}get playing(){return this._playing}get volume(){return this.audio&&this.audio.volume||0}set volume(e){e!==this.volume&&(this.audio&&this.audio.setVolume(e),this._lastVolume=e)}get lastVolume(){return this._lastVolume}set loading(e){this.loading!==e&&(this._loading=e,this.emit(F.loading,this._loading))}get loading(){return this._loading}set recording(e){e?this.playing&&this.recorder&&this.recorder.startRecord():this.recorder&&this.recorder.stopRecordAndSave()}get recording(){return!!this.recorder&&this.recorder.recording}set audioTimestamp(e){null!==e&&(this._audioTimestamp=e)}get audioTimestamp(){return this._audioTimestamp}set videoTimestamp(e){null!==e&&(this._videoTimestamp=e,this._opt.useWCS||this._opt.useMSE||this.audioTimestamp&&this.videoTimestamp&&this.audio&&this.audio.emit(F.videoSyncAudio,{audioTimestamp:this.audioTimestamp,videoTimestamp:this.videoTimestamp,diff:this.audioTimestamp-this.videoTimestamp}))}get videoTimestamp(){return this._videoTimestamp}get isDebug(){return!0===this._opt.debug}updateOption(e){this._opt=Object.assign({},this._opt,e)}init(){return new Promise((e,t)=>{this.stream||(this.stream=new _e(this)),this.audio||this._opt.hasAudio&&(this.audio=new qe(this)),this.demux||(this.demux=new at(this)),this._opt.useWCS&&(this.webcodecsDecoder||(this.webcodecsDecoder=new ct(this))),this._opt.useMSE&&(this.mseDecoder||(this.mseDecoder=new ft(this))),this.decoderWorker||this._onlyMseOrWcsVideo()?e():(this.decoderWorker=new it(this),this.debug.log("Player","waiting decoderWorker init"),this.once(F.decoderWorkerInit,()=>{this.debug.log("Player","decoderWorker init success"),this.loaded=!0,e()}))})}play(e,t){return new Promise((i,o)=>{if(!e&&!this._opt.url)return o();this._closed=!1,this.loading=!0,this.playing=!1,this._times.playInitStart=Se(),e||(e=this._opt.url),this._opt.url=e,this.clearCheckHeartTimeout(),this.init().then(()=>{this._times.playStart=Se(),this._opt.isNotMute&&this.mute(!1),this.webcodecsDecoder&&this.webcodecsDecoder.once(O.webcodecsH265NotSupport,()=>{this.emit(O.webcodecsH265NotSupport),this._opt.autoWasm||this.emit(F.error,O.webcodecsH265NotSupport)}),this.mseDecoder&&(this.mseDecoder.once(O.mediaSourceH265NotSupport,()=>{this.emit(O.mediaSourceH265NotSupport),this._opt.autoWasm||this.emit(F.error,O.mediaSourceH265NotSupport)}),this.mseDecoder.once(O.mediaSourceFull,()=>{this.emitError(O.mediaSourceFull)}),this.mseDecoder.once(O.mediaSourceAppendBufferError,()=>{this.emitError(O.mediaSourceAppendBufferError)}),this.mseDecoder.once(O.mediaSourceBufferListLarge,()=>{this.emitError(O.mediaSourceBufferListLarge)}),this.mseDecoder.once(O.mediaSourceAppendBufferEndTimeout,()=>{this.emitError(O.mediaSourceAppendBufferEndTimeout)})),this.enableWakeLock(),this.stream.fetchStream(e,t),this.checkLoadingTimeout(),this.stream.once(O.fetchError,e=>{this.emitError(O.fetchError,e)}),this.stream.once(O.websocketError,e=>{this.emitError(O.websocketError,e)}),this.stream.once(F.streamEnd,e=>{this.emitError(F.streamEnd,e)}),this.stream.once(F.streamSuccess,()=>{i(),this._times.streamResponse=Se(),this.video.play(),this.checkStatsInterval()})}).catch(e=>{o(e)})})}close(){return new Promise((e,t)=>{this._close().then(()=>{this.video&&this.video.clearView(),e()})})}resumeAudioAfterPause(){this.lastVolume&&(this.volume=this.lastVolume)}_close(){return new Promise((e,t)=>{this._closed=!0,this.stream&&(this.stream.destroy(),this.stream=null),this.demux&&(this.demux.destroy(),this.demux=null),this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.audio&&(this.audio.destroy(),this.audio=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.playing=!1,this.loading=!1,this.recording=!1,this.video&&(this.video.resetInit(),this.video.pause(!0)),this.releaseWakeLock(),this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},setTimeout(()=>{e()},0)})}pause(e=!1){return e?this.close():this._close()}mute(e){if(this.audio){const t=this.audio.getLastVolume();this.audio.mute(e),this._lastVolume=e?0:t||.5}}resize(){this.video.resize()}startRecord(e,t){this.recording||(this.recorder.setFileName(e,t),this.recording=!0)}stopRecordAndSave(){this.recording&&(this.recording=!1)}_hasControl(){let e=!1,t=!1;return Object.keys(this._opt.operateBtns).forEach(e=>{this._opt.operateBtns[e]&&(t=!0)}),(this._opt.showBandwidth||this._opt.text||t)&&(e=!0),e}_onlyMseOrWcsVideo(){return!1===this._opt.hasAudio&&(this._opt.useMSE||this._opt.useWCS&&!this._opt.useOffscreen)}checkHeart(){this.clearCheckHeartTimeout(),this.checkHeartTimeout()}checkHeartTimeout(){this._checkHeartTimeout=setTimeout(()=>{if(this.playing){if(0!==this._stats.fps)return;if(this.isDestroyedOrClosed())return;this.pause().then(()=>{this.emit(F.timeout,F.delayTimeout),this.emit(F.delayTimeout)})}},1e3*this._opt.heartTimeout)}checkStatsInterval(){this._checkStatsInterval=setInterval(()=>{this.updateStats()},1e3)}clearCheckHeartTimeout(){this._checkHeartTimeout&&(clearTimeout(this._checkHeartTimeout),this._checkHeartTimeout=null)}checkLoadingTimeout(){const e=parseFloat((Math.floor(11*Math.random())-5)/10),t=this._opt.loadingTimeout+e;this.debug.log("Player",`checkLoadingTimeout loadingTimeout is ${this._opt.loadingTimeout} and newLoadingTimeout is ${t}`),this._checkLoadingTimeout=setTimeout(()=>{this.playing||this.isDestroyedOrClosed()||this.pause().then(()=>{this.emit(F.timeout,F.loadingTimeout),this.emit(F.loadingTimeout)})},1e3*t)}clearCheckLoadingTimeout(){this._checkLoadingTimeout&&(clearTimeout(this._checkLoadingTimeout),this._checkLoadingTimeout=null)}clearStatsInterval(){this._checkStatsInterval&&(clearInterval(this._checkStatsInterval),this._checkStatsInterval=null)}handleRender(){this.isDestroyedOrClosed()||(this.loading&&(this.emit(F.start),this.loading=!1,this.clearCheckLoadingTimeout()),this.playing||(this.playing=!0),this.checkHeart())}updateStats(e={}){if(this.isDestroyedOrClosed())return;this._startBpsTime||(this._startBpsTime=Se()),De(e.ts)&&(this._stats.ts=e.ts),De(e.buf)&&(this._stats.buf=e.buf),e.fps&&(this._stats.fps+=1),e.abps&&(this._stats.abps+=e.abps),e.vbps&&(this._stats.vbps+=e.vbps);const t=Se();t-this._startBpsTime<1e3||(this.emit(F.stats,this._stats),this.emit(F.performance,function(e){let t=0;return e>=24?t=2:e>=15&&(t=1),t}(this._stats.fps)),this._stats.fps=0,this._stats.abps=0,this._stats.vbps=0,this._startBpsTime=t)}resetStats(){this._startBpsTime=null,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0}}enableWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.enable()}releaseWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.disable()}handlePlayToRenderTimes(){if(this.isDestroyedOrClosed())return;const e=this._times;e.playTimestamp=e.playStart-e.playInitStart,e.streamTimestamp=e.streamStart-e.playStart,e.streamResponseTimestamp=e.streamResponse-e.streamStart,e.demuxTimestamp=e.demuxStart-e.streamResponse,e.decodeTimestamp=e.decodeStart-e.demuxStart,e.videoTimestamp=e.videoStart-e.decodeStart,e.allTimestamp=e.videoStart-e.playInitStart,this.emit(F.playToRenderTimes,e)}getOption(){return this._opt}emitError(e,t=""){this.emit(F.error,e,t),this.emit(e,t)}isControlBarShow(){const e=this._opt.hasControl,t=this._opt.controlAutoHide;let i=e&&!t;return i&&this.control&&(i=this.control.getBarIsShow()),i}getControlBarShow(){let e=!1;return this.control&&(e=this.control.getBarIsShow()),e}toggleControlBar(e){this.control&&(this.control.toggleBar(e),this.resize())}isDestroyed(){return this._destroyed}isClosed(){return this._closed}isDestroyedOrClosed(){return this.isDestroyed()||this.isClosed()}}class St extends Ve{constructor(e){super();let t=e,i=e.container;if(this.TAG_NAME="Jessibuca","string"==typeof e.container&&(i=document.querySelector(e.container)),!i)throw new Error("Jessibuca need container option");if("CANVAS"===i.nodeName||"VIDEO"===i.nodeName)throw new Error(`Jessibuca container type can not be ${i.nodeName} type`);if(t.videoBuffer>=t.heartTimeout)throw new Error(`Jessibuca videoBuffer ${t.videoBuffer}s must be less than heartTimeout ${t.heartTimeout}s`);if(this._checkHasCreated(i))throw new Error("Jessibuca container has been created and can not be created again",i);if(t.videoBuffer>10&&console.warn("Jessibuca",`videoBuffer ${t.videoBuffer}s is too long, will black screen for ${t.videoBuffer}s , it is recommended to set it to less than 10s`),!i.classList)throw new Error("Jessibuca container option must be DOM Element");var o,r,s;i.classList.add("jessibuca-container"),o=i,r=h,s="xxxxxxxxxxxx4xxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)}),o&&(o.dataset?o.dataset[r]=s:o.setAttribute("data-"+r,s)),delete t.container,delete t.url,t.forceNoOffscreen=!0,Te()&&(t.controlAutoHide=!1),De(t.videoBuffer)&&(t.videoBuffer=1e3*Number(t.videoBuffer)),De(t.timeout)&&(Ie(t.loadingTimeout)&&(t.loadingTimeout=t.timeout),Ie(t.heartTimeout)&&(t.heartTimeout=t.timeout)),this._opt=t,this.$container=i,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.initDecoderWorkerTimeout=null,this._destroyed=!1,this.events=new be(this),this.debug=new fe(this),this._initPlayer(i,t),console.log(`Jessibuca version: ${p}`)}async destroy(){var e,t;this._destroyed=!0,this.off(),this._clearInitDecoderWorkerTimeout(),this.player&&(await this.player.destroy(),this.player=null),this.events&&(this.events.destroy(),this.events=null),this.$container&&(this.$container.classList.remove("jessibuca-container"),this.$container.classList.remove("jessibuca-fullscreen-web"),e=this.$container,t=h,e&&(e.dataset?delete e.dataset[t]:e.removeAttribute("data-"+t)),this.$container.innerHTML="",this.$container=null),this._opt={},this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0}_initPlayer(e,t){this.player=new wt(e,t);try{this.debug.log("jessibuca","_initPlayer",JSON.stringify(this.player.getOption()))}catch(e){}this._bindEvents()}_resetPlayer(e={}){this.player.destroy(),this.player=null,this._opt=Object.assign(this._opt,e),this._opt.url="",this._initPlayer(this.$container,this._opt)}_bindEvents(){Object.keys(M).forEach(e=>{this.player.on(M[e],t=>{this.emit(e,t)})})}isDestroyed(){return this._destroyed}setDebug(e){this.debug.log(this.TAG_NAME,"setDebug()",e),this.player.updateOption({debug:!!e})}mute(){this.debug.log(this.TAG_NAME,"mute()"),this.player.mute(!0)}cancelMute(){this.debug.log(this.TAG_NAME,"cancelMute()"),this.player.mute(!1)}setVolume(e){this.debug.log(this.TAG_NAME,"setVolume()",e),this.player.volume=e}audioResume(){this.debug.log(this.TAG_NAME,"audioResume()"),this.player.audio&&this.player.audio.audioEnabled(!0)}setTimeout(e){this.debug.log(this.TAG_NAME,"setTimeout()",e),e=Number(e),this.player.updateOption({timeout:e,loadingTimeout:e,heartTimeout:e})}setScaleMode(e){e=Number(e),this.debug.log(this.TAG_NAME,"setScaleMode()",e);let t={isFullResize:!1,isResize:!1};switch(e){case z:t.isFullResize=!1,t.isResize=!1;break;case Y:t.isFullResize=!1,t.isResize=!0;break;case X:t.isFullResize=!0,t.isResize=!0}this.player.updateOption(t),this.resize()}pause(){return new Promise((e,t)=>{this.debug.log(this.TAG_NAME,"pause()"),this.player?this.player.pause().then(()=>{e()}).catch(e=>{t(e)}):t("player is null")})}async close(){return this.debug.log(this.TAG_NAME,"close()"),await this.destroy(),!0}clearView(){this.debug.log(this.TAG_NAME,"clearView()"),this.player.video.clearView()}play(e,t={}){return new Promise((i,o)=>{try{this.debug.log(this.TAG_NAME,`play() ${e}`,JSON.stringify(t))}catch(i){this.debug.log(this.TAG_NAME,`play() ${e}`,t)}if(!this.isDestroyed())return e||this._opt.url?void(e?this._opt.url?e===this._opt.url?this.player.playing?i():(this.clearView(),this.player.play(this._opt.url,this._opt.playOptions).then(()=>{i(),this.player.resumeAudioAfterPause()}).catch(e=>{this.debug.warn("jessibuca","pause -> play and play error",e),this.player.pause().then(()=>{o(e)})})):this.player.pause().then(()=>{this.clearView(),this._play(e,t).then(()=>{i()}).catch(e=>{this.debug.warn("jessibuca","this._play error",e),o(e)})}).catch(e=>{this.debug.warn("jessibuca","this._opt.url is null and pause error",e),o(e)}):this._play(e,t).then(()=>{i()}).catch(e=>{this.debug.warn("jessibuca","this._play error",e),o(e)}):this.player.play(this._opt.url,this._opt.playOptions).then(()=>{i(),this.player.resumeAudioAfterPause()}).catch(e=>{this.debug.warn("jessibuca","url is null and play error",e),this.player.pause().then(()=>{o(e)})})):(this.emit(F.error,O.playError),void o("play url is empty"));o("Jessibuca is destroyed")})}_play(e,t={}){return new Promise((i,o)=>{this._opt.url=e,this._opt.playOptions=t;const r=0===e.indexOf("http"),s=r?A:n,a=r||-1!==e.indexOf(".flv")||this.player._opt.isFlv?d:c;this.player.updateOption({protocol:s,demuxType:a}),this.player.once(O.webglAlignmentError,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","webglAlignmentError"),this._resetPlayer({openWebglAlignment:!0}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","webglAlignmentError and play success")}).catch(()=>{this.debug.log("Jessibuca","webglAlignmentError and play error")})})}),this.player.once(O.webglContextLostError,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","webglContextLostError and paused")}).catch(()=>{this.debug.warn("Jessibuca","webglContextLostError and paused error")})}),this.player.once(O.webglInitError,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","webglInitError and paused")}).catch(()=>{this.debug.warn("Jessibuca","webglInitError and paused error")})}),this.player.once(O.mediaSourceH265NotSupport,()=>{this.pause().then(()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1,useWCS:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")}).catch(()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play error")})):this.debug.log("Jessibuca","media source h265 not support and paused")})}),this.player.once(O.mediaSourceFull,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","media source full"),this._resetPlayer(),this.play(e,t).then(()=>{this.debug.log("Jessibuca","media source full and reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","media source full and reset player and play error")})})}),this.player.once(O.mediaSourceAppendBufferError,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","media source append buffer error"),this._resetPlayer(),this.play(e,t).then(()=>{this.debug.log("Jessibuca","media source append buffer error and reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","media source append buffer error and reset player and play error")})})}),this.player.once(O.mediaSourceBufferListLarge,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","media source buffer list large"),this._resetPlayer(),this.play(e,t).then(()=>{this.debug.log("Jessibuca","media source buffer list large and reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","media source buffer list large and reset player and play error")})})}),this.player.once(O.mediaSourceAppendBufferEndTimeout,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","media source append buffer end timeout"),this._resetPlayer(),this.play(e,t).then(()=>{this.debug.log("Jessibuca","media source append buffer end timeout and reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","media source append buffer end timeout and reset player and play error")})})}),this.player.once(O.mseSourceBufferError,()=>{this.pause().then(()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","auto wasm [mse-> wasm] reset player and play error")})):this.debug.log("Jessibuca","mse source buffer error and paused")})}),this.player.once(O.webcodecsH265NotSupport,()=>{this.pause().then(()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play"),this._resetPlayer({useWCS:!1,useMSE:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","auto wasm [wcs-> wasm] reset player and play error")})):this.debug.log("Jessibuca","webcodecs h265 not support and paused")})}),this.player.once(O.webcodecsWidthOrHeightChange,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play"),this._resetPlayer({useWCS:!0}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","webcodecs Width Or Height Change reset player and play error")})})}),this.player.once(O.webcodecsDecodeError,()=>{this.pause().then(()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","webcodecs decode error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","webcodecs decode error reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","webcodecs decode error reset player and play error")})):this.debug.log("Jessibuca","webcodecs decode error and paused")})}),this.player.once(O.webcodecsConfigureError,()=>{this.pause().then(()=>{this.player._opt.autoWasm?(this.debug.log("Jessibuca","webcodecs Configure error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","webcodecs Configure error reset player and play success")}).catch(()=>{this.debug.warn("Jessibuca","webcodecs Configure error reset player and play error")})):this.debug.log("Jessibuca","webcodecs Configure error and paused")})}),this.player.once(O.wasmDecodeError,()=>{this.player._opt.wasmDecodeErrorReplay?this.pause().then(()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then(()=>{this.debug.log("Jessibuca","wasm decode error and reset player and play success")}).catch(e=>{this.debug.warn("Jessibuca","wasm decode error and reset player and play error")})}):this.pause().then(()=>{this.debug.log("Jessibuca","wasm decode error and paused")}).catch(e=>{this.debug.warn("Jessibuca","wasm decode error and paused error",e)})}),this.player.once(O.fetchError,e=>{this.pause().then(()=>{this.debug.log("Jessibuca","fetch error and pause play")}).catch(e=>{this.debug.warn("Jessibuca","fetch error and pause play error",e)})}),this.player.once(O.websocketError,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","websocket Error and pause play")}).catch(e=>{this.debug.warn("Jessibuca","websocket Error and pause play error",e)})}),this.player.once(F.streamEnd,()=>{this.pause().then(()=>{this.debug.log("Jessibuca","stream End and pause play")}).catch(e=>{this.debug.warn("Jessibuca","stream End and pause play error",e)})}),this.player.on(F.delayTimeout,()=>{this.player._opt.heartTimeoutReplay&&(this._heartTimeoutReplayTimes{this._heartTimeoutReplayTimes=0}).catch(()=>{}))}),this.player.on(F.loadingTimeout,()=>{this.player._opt.loadingTimeoutReplay&&(this._loadingTimeoutReplayTimes{this._loadingTimeoutReplayTimes=0}).catch(()=>{}))}),this.hasLoaded()?this.player.play(e,t).then(()=>{i()}).catch(e=>{this.debug.warn("Jessibuca","hasLoaded and play error",e),this.player&&this.player.pause().then(()=>{o(e)})}):(this.debug.log("Jessibuca","_play ant waiting decoderWorkerInit"),this._checkInitDecoderWorkerTimeout(),this.player.once(F.decoderWorkerInit,()=>{this._clearInitDecoderWorkerTimeout(),this.isDestroyed()||(this.debug.log("Jessibuca","_play decoderWorkerInit success and play"),this.player.play(e,t).then(()=>{i()}).catch(e=>{this.debug.warn("Jessibuca","decoderWorkerInit and play error",e),this.player&&this.player.pause().then(()=>{o(e)})}))}))})}resize(){this.player.resize()}setBufferTime(e){e=Number(e),this.debug.log(this.TAG_NAME,"setBufferTime()",e),this.player.updateOption({videoBuffer:1e3*e}),this.player.decoderWorker&&this.player.decoderWorker.updateWorkConfig({key:"videoBuffer",value:1e3*e})}setRotate(e){e=parseInt(e,10),this.debug.log(this.TAG_NAME,"setRotate()",e);this.player._opt.rotate!==e&&-1!==[0,90,180,270].indexOf(e)&&(this.player.updateOption({rotate:e}),this.resize())}hasLoaded(){return this.player.loaded}setKeepScreenOn(){this.debug.log(this.TAG_NAME,"setKeepScreenOn()"),this.player.updateOption({keepScreenOn:!0})}setFullscreen(e){this.debug.log(this.TAG_NAME,"setFullscreen()");const t=!!e;this.player.fullscreen!==t&&(this.player.fullscreen=t)}screenshot(e,t,i,o){return this.debug.log(this.TAG_NAME,"screenshot()",e,t,i,o),this.player.video?this.player.video.screenshot(e,t,i,o):""}startRecord(e,t){return new Promise((i,o)=>{this.debug.log(this.TAG_NAME,"startRecord()",e,t),this.player.playing?(this.player.startRecord(e,t),i()):o()})}stopRecordAndSave(){this.debug.log(this.TAG_NAME,"stopRecordAndSave()"),this.player.recording&&this.player.stopRecordAndSave()}isPlaying(){return!!this.player&&this.player.playing}isMute(){return!this.player.audio||this.player.audio.isMute}isRecording(){return this.player.recorder.recording}_checkHasCreated(e){if(!e)return!1;const t=function(e,t){return e?e.dataset?e.dataset[t]:e.getAttribute("data-"+t):""}(e,h);return!!t}toggleControlBar(e){this.isDestroyed()||(this.debug.log(this.TAG_NAME,"toggleControlBar()",e),this.player&&this.player.toggleControlBar(e))}getControlBarShow(){if(this.isDestroyed())return!1;let e=!1;return this.player&&(e=this.player.getControlBarShow()),e}kbps2Speed(e){return Number((1e3*e/8/1024).toFixed(2))}_clearInitDecoderWorkerTimeout(){this.initDecoderWorkerTimeout&&(clearTimeout(this.initDecoderWorkerTimeout),this.initDecoderWorkerTimeout=null)}_checkInitDecoderWorkerTimeout(){this._clearInitDecoderWorkerTimeout(),this.initDecoderWorkerTimeout=setTimeout(()=>{this._handleInitDecoderWorkerTimeout()},1e3*this.player._opt.loadingDecoderWorkerTimeout)}_handleInitDecoderWorkerTimeout(){this.pause().then(()=>{this.debug.log("Jessibuca","init decoder worker timeout and pause play")}).catch(e=>{this.debug.warn("Jessibuca","init decoder worker timeout and pause play error",e)})}}return a(St,"ERROR",O),a(St,"TIMEOUT",{loadingTimeout:F.loadingTimeout,delayTimeout:F.delayTimeout}),window.Jessibuca=St,St}); diff --git a/web/src/api/cloudRecord.js b/web/src/api/cloudRecord.js index dabf7deda..63e0795b0 100644 --- a/web/src/api/cloudRecord.js +++ b/web/src/api/cloudRecord.js @@ -28,14 +28,14 @@ export function queryListByData(params) { } export function loadRecord(params) { - const { app, stream, date } = params + const { app, stream, cloudRecordId } = params return request({ method: 'get', url: `/api/cloud/record/loadRecord`, params: { app: app, stream: stream, - date: date + cloudRecordId: cloudRecordId } }) } diff --git a/web/src/api/commonChannel.js b/web/src/api/commonChannel.js index 38163c5e9..0a9db6178 100644 --- a/web/src/api/commonChannel.js +++ b/web/src/api/commonChannel.js @@ -63,7 +63,7 @@ export function getList(params) { const { page, count, query, online, hasRecordPlan, channelType } = params return request({ method: 'get', - url: `/api/common/channel/list`, + url: '/api/common/channel/list', params: { page: page, count: count, @@ -79,7 +79,7 @@ export function getCivilCodeList(params) { const { page, count, channelType, query, online, civilCode } = params return request({ method: 'get', - url: `/api/common/channel/civilcode/list`, + url: '/api/common/channel/civilcode/list', params: { page: page, count: count, @@ -95,7 +95,7 @@ export function getUnusualCivilCodeList(params) { const { page, count, channelType, query, online } = params return request({ method: 'get', - url: `/api/common/channel/civilCode/unusual/list`, + url: '/api/common/channel/civilCode/unusual/list', params: { page: page, count: count, @@ -110,7 +110,7 @@ export function getUnusualParentList(params) { const { page, count, channelType, query, online } = params return request({ method: 'get', - url: `/api/common/channel/parent/unusual/list`, + url: '/api/common/channel/parent/unusual/list', params: { page: page, count: count, @@ -125,7 +125,7 @@ export function clearUnusualCivilCodeList(params) { const { all, channelIds } = params return request({ method: 'post', - url: `/api/common/channel/civilCode/unusual/clear`, + url: '/api/common/channel/civilCode/unusual/clear', data: { all: all, channelIds: channelIds @@ -137,7 +137,7 @@ export function clearUnusualParentList(params) { const { all, channelIds } = params return request({ method: 'post', - url: `/api/common/channel/parent/unusual/clear`, + url: '/api/common/channel/parent/unusual/clear', data: { all: all, channelIds: channelIds @@ -149,7 +149,7 @@ export function getParentList(params) { const { page, count, channelType, query, online, groupDeviceId } = params return request({ method: 'get', - url: `/api/common/channel/parent/list`, + url: '/api/common/channel/parent/list', params: { page: page, count: count, @@ -165,7 +165,7 @@ export function addToRegion(params) { const { civilCode, channelIds } = params return request({ method: 'post', - url: `/api/common/channel/region/add`, + url: '/api/common/channel/region/add', data: { civilCode: civilCode, channelIds: channelIds @@ -176,7 +176,7 @@ export function addToRegion(params) { export function deleteFromRegion(channels) { return request({ method: 'post', - url: `/api/common/channel/region/delete`, + url: '/api/common/channel/region/delete', data: { channelIds: channels } @@ -187,7 +187,7 @@ export function addDeviceToRegion(params) { const { civilCode, deviceIds } = params return request({ method: 'post', - url: `/api/common/channel/region/device/add`, + url: '/api/common/channel/region/device/add', data: { civilCode: civilCode, deviceIds: deviceIds @@ -198,7 +198,7 @@ export function addDeviceToRegion(params) { export function deleteDeviceFromRegion(deviceIds) { return request({ method: 'post', - url: `/api/common/channel/region/device/delete`, + url: '/api/common/channel/region/device/delete', data: { deviceIds: deviceIds } @@ -209,7 +209,7 @@ export function addToGroup(params) { const { parentId, businessGroup, channelIds } = params return request({ method: 'post', - url: `/api/common/channel/group/add`, + url: '/api/common/channel/group/add', data: { parentId: parentId, businessGroup: businessGroup, @@ -221,7 +221,7 @@ export function addToGroup(params) { export function deleteFromGroup(channels) { return request({ method: 'post', - url: `/api/common/channel/group/delete`, + url: '/api/common/channel/group/delete', data: { channelIds: channels } @@ -232,7 +232,7 @@ export function addDeviceToGroup(params) { const { parentId, businessGroup, deviceIds } = params return request({ method: 'post', - url: `/api/common/channel/group/device/add`, + url: '/api/common/channel/group/device/add', data: { parentId: parentId, businessGroup: businessGroup, @@ -244,7 +244,7 @@ export function addDeviceToGroup(params) { export function deleteDeviceFromGroup(deviceIds) { return request({ method: 'post', - url: `/api/common/channel/group/device/delete`, + url: '/api/common/channel/group/device/delete', data: { deviceIds: deviceIds } @@ -260,3 +260,329 @@ export function playChannel(channelId) { } }) } +export function stopPlayChannel(channelId) { + return request({ + method: 'get', + url: '/api/common/channel/play/stop', + params: { + channelId: channelId + } + }) +} + + +// 前端控制 + +export function setSpeedForScan({ channelId, scanId, speed }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/scan/set/speed', + params: { + channelId: channelId, + scanId: scanId, + speed: speed + } + }) +} + +export function setLeftForScan({ channelId, scanId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/scan/set/left', + params: { + channelId: channelId, + scanId: scanId + } + }) +} + +export function setRightForScan({ channelId, scanId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/scan/set/right', + params: { + channelId: channelId, + scanId: scanId + } + + }) +} + +export function startScan({ channelId, scanId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/scan/start', + params: { + channelId: channelId, + scanId: scanId + } + }) +} + +export function stopScan({ channelId, scanId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/scan/stop', + params: { + channelId: channelId, + scanId: scanId + } + + }) +} + +export function queryPreset(channelId) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/preset/query', + params: { + channelId: channelId + } + }) +} + +export function addPointForCruise({ channelId, tourId, presetId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/point/add', + params: { + channelId: channelId, + tourId: tourId, + presetId: presetId + } + }) +} + +export function deletePointForCruise({ channelId, tourId, presetId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/point/delete', + params: { + channelId: channelId, + tourId: tourId, + presetId: presetId + } + }) +} + +export function setCruiseSpeed({ channelId, tourId, presetId , speed }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/speed', + params: { + channelId: channelId, + tourId: tourId, + presetId: presetId, + speed: speed + } + }) +} + +export function setCruiseTime({ channelId, tourId, presetId, time }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/time', + params: { + channelId: channelId, + tourId: tourId, + presetId: presetId, + time: time + } + }) +} + +export function startCruise({ channelId, tourId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/start', + params: { + channelId: channelId, + tourId: tourId + } + }) +} + +export function stopCruise({ channelId, tourId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/tour/stop', + params: { + channelId: channelId, + tourId: tourId + } + }) +} + +export function addPreset({ channelId, presetId, presetName }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/preset/add', + params: { + channelId: channelId, + presetId: presetId, + presetName: presetName + } + }) +} + +export function callPreset({ channelId, presetId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/preset/call', + params: { + channelId: channelId, + presetId: presetId + } + }) +} + +export function deletePreset({ channelId, presetId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/preset/delete', + params: { + channelId: channelId, + presetId: presetId + } + }) +} + +/** + * command: on 开启, off 关闭 + */ +export function auxiliary({ channelId, command, auxiliaryId }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/auxiliary', + params: { + channelId: channelId, + command: command, + auxiliaryId: auxiliaryId + } + }) +} +/** + * command: on 开启, off 关闭 + */ +export function wiper({ channelId, command }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/wiper', + params: { + channelId: channelId, + command: command + } + }) +} + +export function ptz({ channelId, command, panSpeed, tiltSpeed, zoomSpeed }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/ptz', + params: { + channelId: channelId, + command: command, + panSpeed: panSpeed, + tiltSpeed: tiltSpeed, + zoomSpeed: zoomSpeed + } + }) +} + +export function iris({ channelId, command, speed }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/fi/iris', + params: { + channelId: channelId, + command: command, + speed: speed + } + }) +} + +export function focus({ channelId, command, speed }) { + return request({ + method: 'get', + url: '/api/common/channel/front-end/fi/focus', + params: { + channelId: channelId, + command: command, + speed: speed + } + }) +} +export function queryRecord({ channelId, startTime, endTime }) { + return request({ + method: 'get', + url: '/api/common/channel/playback/query', + params: { + channelId: channelId, + startTime: startTime, + endTime: endTime + } + }) +} +export function playback({ channelId, startTime, endTime }) { + return request({ + method: 'get', + url: '/api/common/channel/playback', + params: { + channelId: channelId, + startTime: startTime, + endTime: endTime + } + }) +} +export function stopPlayback({ channelId, stream }) { + return request({ + method: 'get', + url: '/api/common/channel/playback/stop', + params: { + channelId: channelId, + stream: stream + } + }) +} +export function pausePlayback({ channelId, stream}) { + return request({ + method: 'get', + url: '/api/common/channel/playback/pause', + params: { + channelId: channelId, + stream: stream + } + }) +} +export function resumePlayback({ channelId, stream}) { + return request({ + method: 'get', + url: '/api/common/channel/playback/resume', + params: { + channelId: channelId, + stream: stream + } + }) +} +export function seekPlayback({ channelId, stream, seekTime}) { + return request({ + method: 'get', + url: '/api/common/channel/playback/seek', + params: { + channelId: channelId, + stream: stream, + seekTime: seekTime + } + }) +} +export function speedPlayback({ channelId, stream, speed}) { + return request({ + method: 'get', + url: '/api/common/channel/playback/speed', + params: { + channelId: channelId, + stream: stream, + speed: speed + } + }) +} diff --git a/web/src/api/device.js b/web/src/api/device.js index a8bff4285..fce3f300e 100644 --- a/web/src/api/device.js +++ b/web/src/api/device.js @@ -47,14 +47,22 @@ export function updateDeviceTransport(deviceId, streamMode) { export function setGuard(deviceId) { return request({ method: 'get', - url: `/api/device/control/guard/${deviceId}/SetGuard` + url: `/api/device/control/guard`, + params: { + deviceId: deviceId, + guardCmd: 'SetGuard' + } }) } export function resetGuard(deviceId) { return request({ method: 'get', - url: `/api/device/control/guard/${deviceId}/ResetGuard` + url: `/api/device/control/guard`, + params: { + deviceId: deviceId, + guardCmd: 'ResetGuard' + } }) } @@ -118,6 +126,19 @@ export function queryChannels(deviceId, params) { }) } +export function queryHasStreamChannels(params) { + const {page, count, query} = params + return request({ + method: 'get', + url: `/api/device/query/streams`, + params: { + page: page, + count: count, + query: query + } + }) +} + export function deviceRecord(params) { const { deviceId, channelId, recordCmdStr } = params return request({ @@ -172,12 +193,13 @@ export function changeChannelAudio(params) { } export function updateChannelStreamIdentification(params) { - const { deviceDbId, streamIdentification } = params + const { deviceDbId, streamIdentification, id } = params return request({ method: 'post', url: `/api/device/query/channel/stream/identification/update/`, params: { deviceDbId: deviceDbId, + id: id, streamIdentification: streamIdentification } }) diff --git a/web/src/api/group.js b/web/src/api/group.js index eaa30bd8c..13d4118f9 100644 --- a/web/src/api/group.js +++ b/web/src/api/group.js @@ -48,3 +48,15 @@ export function getPath(params) { } }) } +export function queryTree(params) { + const { page, count, query } = params + return request({ + method: 'get', + url: `/api/group/tree/query`, + params: { + query: query, + page: page, + count: count + } + }) +} diff --git a/web/src/api/jtDevice.js b/web/src/api/jtDevice.js new file mode 100644 index 000000000..4f5210cb7 --- /dev/null +++ b/web/src/api/jtDevice.js @@ -0,0 +1,371 @@ +import request from '@/utils/request' + +// 部标设备API + +export function queryDevices({ page, count, query, online }) { + return request({ + method: 'get', + url: '/api/jt1078/terminal/list', + params: { + page: page, + count: count, + query: query, + online: online + } + }) +} + +export function queryDeviceById(deviceId) { + return request({ + method: 'get', + url: '/api/jt1078/terminal/query', + params: { + deviceId: deviceId + } + }) +} + +export function update(form) { + return request({ + method: 'post', + url: '/api/jt1078/terminal/update', + params: form + }) +} + +export function add(form) { + return request({ + method: 'post', + url: '/api/jt1078/terminal/add', + params: form + }) +} + +export function deleteDevice(phoneNumber) { + return request({ + method: 'delete', + url: '/api/jt1078/terminal/delete', + params: { + phoneNumber: phoneNumber + } + }) +} + +export function queryChannels(params) { + const { page, count, query, deviceId } = params + return request({ + method: 'get', + url: '/api/jt1078/terminal/channel/list', + params: { + page: page, + count: count, + query: query, + deviceId: deviceId + } + }) +} + +export function play(params) { + const { phoneNumber, channelId, type } = params + return request({ + method: 'get', + url: '/api/jt1078/live/start', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + type: type + } + }) +} +export function stopPlay(params) { + const { phoneNumber, channelId } = params + return request({ + method: 'get', + url: '/api/jt1078/live/stop', + params: { + phoneNumber: phoneNumber, + channelId: channelId + } + }) +} +export function updateChannel(data) { + return request({ + method: 'post', + url: '/api/jt1078/terminal/channel/update', + data: data + }) +} +export function addChannel(data) { + return request({ + method: 'post', + url: '/api/jt1078/terminal/channel/add', + data: data + }) +} + +export function ptz(params) { + const { phoneNumber, channelId, command, speed } = params + return request({ + method: 'get', + url: '/api/jt1078/ptz', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + command: command, + speed: speed + } + }) +} +export function wiper(params) { + const { phoneNumber, channelId, command } = params + return request({ + method: 'get', + url: '/api/jt1078/wiper', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + command: command + } + }) +} +export function fillLight(params) { + const { phoneNumber, channelId, command } = params + return request({ + method: 'get', + url: '/api/jt1078/fill-light', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + command: command + } + }) +} +export function queryRecordList(params) { + const { phoneNumber, channelId, startTime, endTime } = params + return request({ + method: 'get', + url: '/api/jt1078/record/list', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + startTime: startTime, + endTime: endTime + } + }) +} +export function startPlayback(params) { + const { phoneNumber, channelId, startTime, endTime, type, rate, playbackType, playbackSpeed } = params + return request({ + method: 'get', + url: '/api/jt1078/playback/start/', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + startTime: startTime, + endTime: endTime, + type: type, + rate: rate, + playbackType: playbackType, + playbackSpeed: playbackSpeed + } + }) +} +export function getRecordTempUrl({ phoneNumber, channelId, startTime, endTime, alarmSign, mediaType, streamType, storageType }) { + return request({ + method: 'get', + url: '/api/jt1078/playback/downloadUrl', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + startTime: startTime, + endTime: endTime, + alarmSign: alarmSign, + mediaType: mediaType, + streamType: streamType, + storageType: storageType + } + }) +} +export function controlPlayback(params) { + const { phoneNumber, channelId, command, playbackSpeed, time } = params + return request({ + method: 'get', + url: '/api/jt1078/playback/control', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + command: command, + playbackSpeed: playbackSpeed, + time: time + } + }) +} +export function stopPlayback(params) { + const { phoneNumber, channelId, streamId } = params + return request({ + method: 'get', + url: '/api/jt1078/playback/stop/', + params: { + phoneNumber: phoneNumber, + channelId: channelId, + streamId: streamId + } + }) +} +export function queryConfig(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/config/get', + params: { + phoneNumber: phoneNumber + } + }) +} +export function setConfig(data) { + return request({ + method: 'post', + url: '/api/jt1078/config/set', + data: data + }) +} +export function queryAttribute(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/attribute', + params: { + phoneNumber: phoneNumber + } + }) +} +export function linkDetection(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/link-detection', + params: { + phoneNumber: phoneNumber + } + }) +} +export function queryPosition(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/position-info', + params: { + phoneNumber: phoneNumber + } + }) +} +export function sendTextMessage(data) { + return request({ + method: 'post', + url: '/api/jt1078/text-msg', + data: data + }) +} +export function telephoneCallback({ phoneNumber, sign, destPhoneNumber }) { + return request({ + method: 'get', + url: '/api/jt1078/telephone-callback', + params: { + phoneNumber: phoneNumber, + sign: sign, + destPhoneNumber: destPhoneNumber + } + }) +} +export function queryDriverInfo(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/driver-information', + params: { + phoneNumber: phoneNumber + } + }) +} +export function factoryReset(phoneNumber) { + return request({ + method: 'post', + url: '/api/jt1078/control/factory-reset', + params: { + phoneNumber: phoneNumber + } + }) +} +export function reset(phoneNumber) { + return request({ + method: 'post', + url: '/api/jt1078/control/reset', + params: { + phoneNumber: phoneNumber + } + }) +} +export function connection(data) { + return request({ + method: 'post', + url: '/api/jt1078/control/connection', + data: data + }) +} +export function controlDoor({ phoneNumber, open}) { + return request({ + method: 'get', + url: '/api/jt1078/control/door', + params: { + phoneNumber: phoneNumber, + open: open + } + }) +} +export function queryMediaAttribute(phoneNumber) { + return request({ + method: 'get', + url: '/api/jt1078/media/attribute', + params: { + phoneNumber: phoneNumber + } + }) +} +export function queryMediaData(data) { + return request({ + method: 'post', + url: '/api/jt1078/media/list', + data: data + }) +} +export function setPhoneBook(data) { + return request({ + method: 'post', + url: '/api/jt1078/set-phone-book', + data: data + }) +} +export function shooting(data) { + return request({ + method: 'post', + url: '/api/jt1078/shooting', + data: data + }) +} +export function startTalk({ phoneNumber, channelId }) { + return request({ + method: 'get', + url: '/api/jt1078/talk/start', + params: { + phoneNumber: phoneNumber, + channelId: channelId + } + }) +} +export function stopTalk({ phoneNumber, channelId }) { + return request({ + method: 'get', + url: '/api/jt1078/talk/stop', + params: { + phoneNumber: phoneNumber, + channelId: channelId + } + }) +} + + diff --git a/web/src/api/region.js b/web/src/api/region.js index 8de029dc0..de5f9948f 100644 --- a/web/src/api/region.js +++ b/web/src/api/region.js @@ -81,4 +81,16 @@ export function queryPath(deviceId) { } }) } +export function queryTree(params) { + const { page, count, query } = params + return request({ + method: 'get', + url: `/api/region/tree/query`, + params: { + query: query, + page: page, + count: count + } + }) +} diff --git a/web/src/icons/svg/devices.svg b/web/src/icons/svg/devices.svg new file mode 100644 index 000000000..c7e7fc1f6 --- /dev/null +++ b/web/src/icons/svg/devices.svg @@ -0,0 +1 @@ + diff --git a/web/src/icons/svg/devices1.svg b/web/src/icons/svg/devices1.svg new file mode 100644 index 000000000..10ded3695 --- /dev/null +++ b/web/src/icons/svg/devices1.svg @@ -0,0 +1 @@ + diff --git a/web/src/icons/svg/jtDevice.svg b/web/src/icons/svg/jtDevice.svg new file mode 100644 index 000000000..300fee64d --- /dev/null +++ b/web/src/icons/svg/jtDevice.svg @@ -0,0 +1 @@ + diff --git a/web/src/layout/components/TagsView/index.vue b/web/src/layout/components/TagsView/index.vue index d3209b2e7..c82555ded 100644 --- a/web/src/layout/components/TagsView/index.vue +++ b/web/src/layout/components/TagsView/index.vue @@ -17,10 +17,10 @@
    -
  • Refresh
  • -
  • Close
  • -
  • Close Others
  • -
  • Close All
  • +
  • 刷新
  • +
  • 关闭
  • +
  • 关闭其他
  • +
  • 关闭所有
diff --git a/web/src/main.js b/web/src/main.js index 4f72d0d69..f4b9f0770 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -40,9 +40,10 @@ Vue.use(VueClipboard) Vue.config.productionTip = false Vue.prototype.$channelTypeList = { - 1: { id: 1, name: '国标设备', style: { color: '#409eff', borderColor: '#b3d8ff' }}, - 2: { id: 2, name: '推流设备', style: { color: '#67c23a', borderColor: '#c2e7b0' }}, - 3: { id: 3, name: '拉流代理', style: { color: '#e6a23c', borderColor: '#f5dab1' }} + 1: { id: 1, name: '国标设备', style: { color: '#409eff', borderColor: '#b3d8ff' } }, + 2: { id: 2, name: '推流设备', style: { color: '#67c23a', borderColor: '#c2e7b0' } }, + 3: { id: 3, name: '拉流代理', style: { color: '#e6a23c', borderColor: '#f5dab1' } }, + 200: { id: 200, name: '部标设备', style: { color: '#fa6436', borderColor: '#f4997c' } } } new Vue({ diff --git a/web/src/router/index.js b/web/src/router/index.js index b3304a6e4..fd6f29702 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -66,46 +66,65 @@ export const constantRoutes = [ meta: { title: '分屏监控', icon: 'live' } }] }, + { + path: '/channel', + component: Layout, + redirect: '/channel', + onlyIndex: 0, + children: [{ + path: '/channel', + name: 'Channel', + component: () => import('@/views/channel/index'), + meta: { title: '通道列表', icon: 'channelManger'} + }, + { + path: '/channel/record/:channelId', + name: 'CommonRecord', + component: () => import('@/views/channel/record'), + meta: { title: '设备录像' } + } + ] + }, { path: '/device', component: Layout, - redirect: '/device', - onlyIndex: 0, + name: '设备接入', + meta: { title: '设备接入', icon: 'devices' }, children: [ { - path: '', + path: '/device', name: 'Device', component: () => import('@/views/device/index'), meta: { title: '国标设备', icon: 'device' } }, { + hidden: true, path: '/device/record/:deviceId/:channelDeviceId', name: 'DeviceRecord', component: () => import('@/views/device/channel/record'), meta: { title: '国标录像' } - } - ] - }, - { - path: '/push', - component: Layout, - redirect: '/push', - children: [ + }, { - path: '', + path: '/jtDevice', + name: 'JTDevice', + component: () => import('@/views/jtDevice/index'), + meta: { title: '部标设备', icon: 'jtDevice' } + }, + { + hidden: true, + path: '/jtDevice/record/:phoneNumber/:channelId', + name: 'JTDeviceRecord', + component: () => import('@/views/jtDevice/channel/record'), + meta: { title: '部标录像' } + }, + { + path: '/push', name: 'PushList', component: () => import('@/views/streamPush/index'), meta: { title: '推流列表', icon: 'streamPush' } - } - ] - }, - { - path: '/proxy', - component: Layout, - redirect: '/proxy', - children: [ + }, { - path: '', + path: '/proxy', name: 'Proxy', component: () => import('@/views/streamProxy/index'), meta: { title: '拉流代理', icon: 'streamProxy' } diff --git a/web/src/store/index.js b/web/src/store/index.js index 385592902..138c4abed 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -22,6 +22,7 @@ import userApiKeys from './modules/userApiKeys' import gbRecord from './modules/gbRecord' import log from './modules/log' import frontEnd from './modules/frontEnd' +import jtDevice from './modules/jtDevice' Vue.use(Vuex) @@ -47,7 +48,8 @@ const store = new Vuex.Store({ userApiKeys, gbRecord, log, - frontEnd + frontEnd, + jtDevice }, getters }) diff --git a/web/src/store/modules/commonChanel.js b/web/src/store/modules/commonChanel.js index 90e59c81a..e1a84442b 100644 --- a/web/src/store/modules/commonChanel.js +++ b/web/src/store/modules/commonChanel.js @@ -15,7 +15,40 @@ import { clearUnusualCivilCodeList, getIndustryList, getTypeList, - getNetworkIdentificationList, playChannel, addToRegion, deleteFromRegion, addToGroup, deleteFromGroup + getNetworkIdentificationList, + playChannel, + addToRegion, + deleteFromRegion, + addToGroup, + deleteFromGroup, + getList, + addPointForCruise, + addPreset, + auxiliary, + callPreset, + deletePointForCruise, + deletePreset, + focus, + iris, + ptz, + queryPreset, + setCruiseSpeed, + setCruiseTime, + setLeftForScan, + setRightForScan, + setSpeedForScan, + startCruise, + startScan, + stopCruise, + stopScan, + wiper, + stopPlayChannel, + queryRecord, + playback, + stopPlayback, + pausePlayback, + resumePlayback, + seekPlayback, speedPlayback } from '@/api/commonChannel' const actions = { @@ -238,6 +271,296 @@ const actions = { reject(error) }) }) + }, + stopPlayChannel({ commit }, channelId) { + return new Promise((resolve, reject) => { + stopPlayChannel(channelId).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + getList({ commit }, param) { + return new Promise((resolve, reject) => { + getList(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setSpeedForScan({ commit }, params) { + return new Promise((resolve, reject) => { + setSpeedForScan(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setLeftForScan({ commit }, params) { + return new Promise((resolve, reject) => { + setLeftForScan(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setRightForScan({ commit }, params) { + return new Promise((resolve, reject) => { + setRightForScan(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + startScan({ commit }, params) { + return new Promise((resolve, reject) => { + startScan(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopScan({ commit }, params) { + return new Promise((resolve, reject) => { + stopScan(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + addPointForCruise({ commit }, params) { + return new Promise((resolve, reject) => { + addPointForCruise(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + deletePointForCruise({ commit }, params) { + return new Promise((resolve, reject) => { + deletePointForCruise(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setCruiseSpeed({ commit }, params) { + return new Promise((resolve, reject) => { + setCruiseSpeed(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setCruiseTime({ commit }, params) { + return new Promise((resolve, reject) => { + setCruiseTime(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + startCruise({ commit }, params) { + return new Promise((resolve, reject) => { + startCruise(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopCruise({ commit }, params) { + return new Promise((resolve, reject) => { + stopCruise(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + addPreset({ commit }, params) { + return new Promise((resolve, reject) => { + addPreset(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryPreset({ commit }, params) { + return new Promise((resolve, reject) => { + queryPreset(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + callPreset({ commit }, params) { + return new Promise((resolve, reject) => { + callPreset(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + deletePreset({ commit }, params) { + return new Promise((resolve, reject) => { + deletePreset(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + auxiliary({ commit }, params) { + return new Promise((resolve, reject) => { + auxiliary(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + wiper({ commit }, params) { + return new Promise((resolve, reject) => { + wiper(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + ptz({ commit }, params) { + return new Promise((resolve, reject) => { + ptz(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + iris({ commit }, params) { + return new Promise((resolve, reject) => { + iris(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + focus({ commit }, params) { + return new Promise((resolve, reject) => { + focus(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryRecord({ commit }, params) { + return new Promise((resolve, reject) => { + queryRecord(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + playback({ commit }, params) { + return new Promise((resolve, reject) => { + playback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + stopPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + pausePlayback({ commit }, params) { + return new Promise((resolve, reject) => { + pausePlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + resumePlayback({ commit }, params) { + return new Promise((resolve, reject) => { + resumePlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + seekPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + seekPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + speedPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + speedPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) } } diff --git a/web/src/store/modules/device.js b/web/src/store/modules/device.js index a4b6286c2..d9265249a 100644 --- a/web/src/store/modules/device.js +++ b/web/src/store/modules/device.js @@ -1,17 +1,24 @@ import { - add, changeChannelAudio, + add, + changeChannelAudio, deleteDevice, deviceRecord, queryBasicParam, queryChannelOne, - queryChannels, queryChannelTree, queryDeviceOne, + queryChannels, + queryChannelTree, + queryDeviceOne, queryDevices, - queryDeviceSyncStatus, queryDeviceTree, + queryDeviceSyncStatus, + queryDeviceTree, + queryHasStreamChannels, resetGuard, setGuard, subscribeCatalog, subscribeMobilePosition, - sync, update, updateChannelStreamIdentification, + sync, + update, + updateChannelStreamIdentification, updateDeviceTransport } from '@/api/device' @@ -126,6 +133,16 @@ const actions = { }) }) }, + queryHasStreamChannels({commit}, params) { + return new Promise((resolve, reject) => { + queryHasStreamChannels(params).then(response => { + const {data} = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, deviceRecord({ commit }, params) { return new Promise((resolve, reject) => { deviceRecord(params).then(response => { diff --git a/web/src/store/modules/group.js b/web/src/store/modules/group.js index 36d4b14c7..6fec46003 100644 --- a/web/src/store/modules/group.js +++ b/web/src/store/modules/group.js @@ -1,7 +1,7 @@ import { getTreeList, update, - add, deleteGroup, getPath + add, deleteGroup, getPath, queryTree } from '@/api/group' const actions = { @@ -54,6 +54,16 @@ const actions = { reject(error) }) }) + }, + queryTree({ commit }, param) { + return new Promise((resolve, reject) => { + queryTree(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) } } diff --git a/web/src/store/modules/jtDevice.js b/web/src/store/modules/jtDevice.js new file mode 100644 index 000000000..65810b802 --- /dev/null +++ b/web/src/store/modules/jtDevice.js @@ -0,0 +1,402 @@ +import { + add, + addChannel, + connection, + controlDoor, + controlPlayback, + deleteDevice, + factoryReset, + fillLight, + getRecordTempUrl, + linkDetection, + play, + ptz, + queryAttribute, + queryChannels, + queryConfig, + queryDeviceById, + queryDevices, + queryDriverInfo, + queryMediaAttribute, queryMediaData, + queryPosition, + queryRecordList, + reset, + sendTextMessage, + setConfig, setPhoneBook, shooting, + startPlayback, startTalk, + stopPlay, + stopPlayback, stopTalk, + telephoneCallback, + update, + updateChannel, + wiper +} from '@/api/jtDevice' + +const actions = { + queryDevices({ commit }, params) { + return new Promise((resolve, reject) => { + queryDevices(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + add({ commit }, params) { + return new Promise((resolve, reject) => { + add(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + update({ commit }, params) { + return new Promise((resolve, reject) => { + update(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryDeviceById({ commit }, deviceId) { + return new Promise((resolve, reject) => { + queryDeviceById(deviceId).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + deleteDevice({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + deleteDevice(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryChannels({ commit }, params) { + return new Promise((resolve, reject) => { + queryChannels(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + play({ commit }, params) { + return new Promise((resolve, reject) => { + play(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopPlay({ commit }, params) { + return new Promise((resolve, reject) => { + stopPlay(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + updateChannel({ commit }, data) { + return new Promise((resolve, reject) => { + updateChannel(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + addChannel({ commit }, data) { + return new Promise((resolve, reject) => { + addChannel(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + ptz({ commit }, params) { + return new Promise((resolve, reject) => { + ptz(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + wiper({ commit }, params) { + return new Promise((resolve, reject) => { + wiper(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + fillLight({ commit }, params) { + return new Promise((resolve, reject) => { + fillLight(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryConfig({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + queryConfig(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setConfig({ commit }, data) { + return new Promise((resolve, reject) => { + setConfig(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryRecordList({ commit }, params) { + return new Promise((resolve, reject) => { + queryRecordList(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + startPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + startPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + controlPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + controlPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopPlayback({ commit }, params) { + return new Promise((resolve, reject) => { + stopPlayback(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + getRecordTempUrl({ commit }, params) { + return new Promise((resolve, reject) => { + getRecordTempUrl(params).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryAttribute({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + queryAttribute(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + linkDetection({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + linkDetection(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryPosition({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + queryPosition(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + sendTextMessage({ commit }, data) { + return new Promise((resolve, reject) => { + sendTextMessage(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + telephoneCallback({ commit }, param) { + return new Promise((resolve, reject) => { + telephoneCallback(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryDriverInfo({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + queryDriverInfo(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + factoryReset({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + factoryReset(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + reset({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + reset(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + connection({ commit }, data) { + return new Promise((resolve, reject) => { + connection(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + controlDoor({ commit }, param) { + return new Promise((resolve, reject) => { + controlDoor(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryMediaAttribute({ commit }, phoneNumber) { + return new Promise((resolve, reject) => { + queryMediaAttribute(phoneNumber).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + setPhoneBook({ commit }, data) { + return new Promise((resolve, reject) => { + setPhoneBook(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + queryMediaData({ commit }, data) { + return new Promise((resolve, reject) => { + queryMediaData(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + shooting({ commit }, data) { + return new Promise((resolve, reject) => { + shooting(data).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + startTalk({ commit }, param) { + return new Promise((resolve, reject) => { + startTalk(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + stopTalk({ commit }, param) { + return new Promise((resolve, reject) => { + stopTalk(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) + } +} + +export default { + namespaced: true, + actions +} + diff --git a/web/src/store/modules/region.js b/web/src/store/modules/region.js index 82c7957f8..f28bbb309 100644 --- a/web/src/store/modules/region.js +++ b/web/src/store/modules/region.js @@ -6,7 +6,7 @@ import { queryChildListInBase, update, add, - queryPath + queryPath, queryTree } from '@/api/region' const actions = { @@ -89,6 +89,16 @@ const actions = { reject(error) }) }) + }, + queryTree({ commit }, param) { + return new Promise((resolve, reject) => { + queryTree(param).then(response => { + const { data } = response + resolve(data) + }).catch(error => { + reject(error) + }) + }) } } diff --git a/web/src/styles/iconfont.scss b/web/src/styles/iconfont.scss index 1a1c754a0..646f3aa00 100644 --- a/web/src/styles/iconfont.scss +++ b/web/src/styles/iconfont.scss @@ -1,6 +1,6 @@ @font-face { font-family: "iconfont"; /* Project id 1291092 */ - src: url('iconfont.woff2?t=1743052226670') format('woff2'); + src: url('iconfont.woff2?t=1758456390170') format('woff2') } .iconfont { @@ -11,6 +11,34 @@ -moz-osx-font-smoothing: grayscale; } +.icon-a-bofangqi1:before { + content: "\ec17"; +} + +.icon-sanjiaoxing:before { + content: "\e7f1"; +} + +.icon-icon_gps:before { + content: "\e7f0"; +} + +.icon-yidingdaoweizhuangtai:before { + content: "\e7ef"; +} + +.icon-gps:before { + content: "\e8b6"; +} + +.icon-tongdao:before { + content: "\e7ee"; +} + +.icon-xiazailiebiao:before { + content: "\e7ed"; +} + .icon-zoom-in:before { content: "\e7eb"; } diff --git a/web/src/styles/iconfont.woff2 b/web/src/styles/iconfont.woff2 index 27b804266..6af37da81 100644 Binary files a/web/src/styles/iconfont.woff2 and b/web/src/styles/iconfont.woff2 differ diff --git a/web/src/utils/auth.js b/web/src/utils/auth.js index 2a885d1de..6e9050167 100644 --- a/web/src/utils/auth.js +++ b/web/src/utils/auth.js @@ -3,6 +3,7 @@ import Cookies from 'js-cookie' const TokenKey = 'wvp_token' const NameKey = 'wvp_username' const serverIdKey = 'wvp_server_id' +const expires = 30 export function getToken() { console.log('Getting token...') @@ -10,7 +11,7 @@ export function getToken() { } export function setToken(token) { - return Cookies.set(TokenKey, token) + return Cookies.set(TokenKey, token, {expires: expires}) } export function removeToken() { @@ -22,7 +23,7 @@ export function getName() { } export function setName(name) { - return Cookies.set(NameKey, name) + return Cookies.set(NameKey, name, {expires: expires}) } export function removeName() { @@ -34,7 +35,7 @@ export function getServerId() { } export function setServerId(serverId) { - return Cookies.set(serverIdKey, serverId) + return Cookies.set(serverIdKey, serverId, {expires: expires}) } export function removeServerId() { diff --git a/web/src/views/channel/edit.vue b/web/src/views/channel/edit.vue new file mode 100644 index 000000000..348c3d1da --- /dev/null +++ b/web/src/views/channel/edit.vue @@ -0,0 +1,30 @@ + + + diff --git a/web/src/views/channel/group/index.vue b/web/src/views/channel/group/index.vue index 487c4f7f2..1484b6031 100755 --- a/web/src/views/channel/group/index.vue +++ b/web/src/views/channel/group/index.vue @@ -4,8 +4,8 @@ ref="groupTree" :show-header="true" :edit="true" - :click-event="treeNodeClickEvent" - :on-channel-change="onChannelChange" + @clickEvent="treeNodeClickEvent" + @onChannelChange="onChannelChange" :enable-add-channel="true" :add-channel-to-group="addChannelToGroup" /> @@ -20,7 +20,7 @@
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + diff --git a/web/src/views/channel/record.vue b/web/src/views/channel/record.vue new file mode 100755 index 000000000..bd01a3235 --- /dev/null +++ b/web/src/views/channel/record.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/web/src/views/channel/region/index.vue b/web/src/views/channel/region/index.vue index a61c291a4..9a846c65b 100755 --- a/web/src/views/channel/region/index.vue +++ b/web/src/views/channel/region/index.vue @@ -4,8 +4,8 @@ ref="regionTree" :show-header="true" :edit="true" - :click-event="treeNodeClickEvent" - :on-channel-change="onChannelChange" + @clickEvent="treeNodeClickEvent" + @onChannelChange="onChannelChange" :enable-add-channel="true" :add-channel-to-civil-code="addChannelToCivilCode" /> @@ -20,7 +20,7 @@
+
+
+ + +
+
+
+ {{showPlayTimeValue}} +
+
+
+
+ +
{{showPlayTimeTitle}}
+
+
+
+
+ {{showPlayTimeTotal}} +
+
+ +
+ + + + + diff --git a/web/src/views/cloudRecord/detail.vue b/web/src/views/cloudRecord/detail.vue index 897ed1502..b3eed0012 100755 --- a/web/src/views/cloudRecord/detail.vue +++ b/web/src/views/cloudRecord/detail.vue @@ -1,5 +1,5 @@ diff --git a/web/src/views/common/DeviceTree.vue b/web/src/views/common/DeviceTree.vue index edf71ff92..fe99ca76d 100755 --- a/web/src/views/common/DeviceTree.vue +++ b/web/src/views/common/DeviceTree.vue @@ -1,5 +1,5 @@