From 4d34d51f404a4024b2d0e6d68142adf2a2c4ca72 Mon Sep 17 00:00:00 2001 From: "https://blog.iamtsm.cn" <1905333456@qq.com> Date: Wed, 16 Aug 2023 20:25:51 +0800 Subject: [PATCH] feat: audio room feat: show room type feat: update doc fix: socket default host port err fix: log err --- README.md | 247 ++++++++------- doc/README_EN.md | 337 +++++++++------------ svr/conf/cfg.json | 2 +- svr/res/css/index.css | 15 + svr/res/index.html | 74 ++++- svr/res/js/audioShare.js | 192 ++++++++++++ svr/res/js/comm.js | 17 ++ svr/res/js/index.js | 200 +++++++++++- svr/res/js/language.js | 18 ++ svr/res/js/videoShare.js | 2 +- svr/src/bussiness/manage/settingPage.js | 5 + svr/src/bussiness/notify/notifyHandler.js | 31 ++ svr/src/controller/comm/comm.js | 2 +- svr/src/dao/room/room.js | 1 + svr/src/socket/rtcConstant.js | 4 + svr/src/socket/rtcCreateJoin/createJoin.js | 8 +- svr/src/socket/rtcMessage/message.js | 22 ++ svr/static/layui/font-ext/demo_index.html | 98 +++++- svr/static/layui/font-ext/iconfont.css | 22 +- svr/static/layui/font-ext/iconfont.js | 2 +- svr/static/layui/font-ext/iconfont.json | 28 ++ svr/static/layui/font-ext/iconfont.ttf | Bin 36356 -> 37592 bytes svr/static/layui/font-ext/iconfont.woff | Bin 21284 -> 22144 bytes svr/static/layui/font-ext/iconfont.woff2 | Bin 18196 -> 18840 bytes 24 files changed, 997 insertions(+), 330 deletions(-) create mode 100644 svr/res/js/audioShare.js diff --git a/README.md b/README.md index 38bf8d7..113ac7b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![](https://img.shields.io/badge/deployment-private-yellow)](https://github.com/iamtsm/tl-rtc-file/) [![](https://img.shields.io/badge/platform-unlimited-coral)](https://github.com/iamtsm/tl-rtc-file/) -

体验地址DockerHub | @@ -16,171 +15,125 @@ ## 目录 -- [背景-简介](#背景-简介) -- [优点-扩展](#优点-扩展) -- [修改配置](#修改配置) +- [背景](#背景) +- [优点](#优点) +- [部署前必看](#部署前必看) - [自行部署](#自行部署) - [安装环境](#安装环境) - - [准备启动](#准备启动) - - [配置数据库 (非必须步骤)](#配置数据库-非必须步骤) - - [管理后台 (非必须步骤)](#管理后台-非必须步骤) - - [企微通知 (非必须步骤)](#企微通知-非必须步骤) - - [OSS云存储 (非必须步骤)](#oss云存储-非必须步骤) - - [Chat-GPT (非必须步骤)](#chat-gpt-非必须步骤) - - [配置turnserver (局域网非必须步骤,公网必须步骤)](#配置turnserver-局域网非必须步骤公网必须步骤) -- [Docker部署](#Docker部署) + - [启动服务](#启动服务) +- [docker部署](#docker部署) + - [docker一键脚本启动](#docker一键脚本启动) + - [docker-compose命令启动](#docker-compose启动) + - [自行打包镜像启动](#自行打包启动镜像) - [其他形式部署](#其他形式部署) +- [配置数据库 (非必须步骤)](#配置数据库-非必须步骤) +- [管理后台 (非必须步骤)](#管理后台-非必须步骤) +- [企微通知 (非必须步骤)](#企微通知-非必须步骤) +- [OSS云存储 (非必须步骤)](#oss云存储-非必须步骤) +- [Chat-GPT (非必须步骤)](#chat-gpt-非必须步骤) +- [配置turnserver (局域网非必须步骤,公网必须步骤)](#配置turnserver-局域网非必须步骤公网必须步骤) - [概述图](#概述图) - [License](#license) - [免责声明](#免责声明) -### 背景-简介 +## 背景 20年毕设的题目相关整理出来的,用webrt在web端传输文件,支持传输超大文件。 -### 优点-扩展 +## 优点 分片传输,跨终端,不限平台,方便使用,内网不限速(局域网最高到过70多M/s),支持私有部署,支持多文件拖拽发送,网页文件预览。 扩展了许多丰富的小功能,如本地屏幕录制,远程屏幕共享(无延迟),远程音视频通话(无延迟),直播(无延迟),密码房间,oss云存储,中继服务设置,webrtc检测,webrtc统计,文字传输(群聊,私聊),公共聊天,远程画板,AI聊天框,丰富的后台管理,实时执行日志展示,机器人告警通知等功能... 等等 -## 修改配置 +## 部署前必看 无论是自行部署,还是docker部署,还是其他脚本部署,都需要先行修改 `tlrtcfile.env` 中相应配置,再执行下面操作,且后续还需修改配置,需要重启服务 +当然,你也可以不修改配置,使用默认的配置,但是默认的配置仅限于可以在localhost测试使用,其他人访问不到也使用不了。所以如果是需要部署到服务器上给局域网或者公网其他用户使用,就必须按需设置好 `tlrtcfile.env` + + ## 自行部署 #### 安装环境 -1.安装node-14.21.x或14.21.x以上,npm后,进入项目目录运行下面命令 +安装node-14.21.x或14.21.x以上,npm后,进入项目目录运行下面命令 ``` cd svr/ + npm install ``` -2.首次运行/自行开发页面,用下面两个命令之一即可 +首次运行执行一次下面的命令 -`npm run build:dev` (如果你需要自己开发/修改前端页面,用这个命令) -`npm run build:pro` (不需要开发/修改前端页面,用这个命令) +``` +npm run build:pro +``` -3.修改 `tlrtcfile.env` 配置文件 +如果你需要自己开发/修改前端页面,用这个命令,不需要开发页面就跳过这一个 -#### 准备启动 +``` +npm run build:dev +``` + +#### 启动服务 启动以下两个服务, 选一种模式启动即可,两者的区别就是,https环境启动才可以使用音视频,直播,屏幕共享功能,其他功能不影响 -http模式启动后,访问 http://你的机器ip:9092 即可 +http模式启动后,访问 `http://你的机器ip:9092` -- api服务: `npm run http-api` -- socket服务 : `npm run http-socket` +- 启动api服务 和 socket服务 -https模式启动后,访问 https://你的机器ip:9092 即可 +``` +npm run http-api -- api服务: `npm run https-api` -- socket服务 : `npm run https-socket` - -#### 配置数据库 (非必须步骤) - -修改 `tlrtcfile.env` 中的数据库相关配置即可 - -#### 企微通知 (非必须步骤) - -修改 `tlrtcfile.env` 中的企业微信通知相关配置即可 - -#### OSS云存储 (非必须步骤) - -修改 `tlrtcfile.env` 中的OSS存储相关配置即可 - -#### Chat-GPT (非必须步骤) - -修改 `tlrtcfile.env` 中的openai相关配置即可 - -#### 管理后台 (非必须步骤) - -前提 : 需要开启数据库配置 - -修改 `tlrtcfile.env` 中的管理后台相关配置即可, 启动后,输入配置的房间号,输入密码,即可进入管理后台 - -#### 配置turnserver (局域网非必须步骤,公网必须步骤) - -目前有两种形式去生成使用turn服务的帐号密码,一种是固定帐号密码 (优先推荐),一种是有效期帐号密码。**选一种方式即可** - -ubuntu示例: - -- 安装coturn `sudo apt-get install coturn` - -有效帐号密码 : `docker/coturn/turnserver-with-secret-user.conf` - -1. 修改 `listening-device`, `listening-ip`, `external-ip`, `static-auth-secret`, `realm` 几个字段即可 -2. 启动turnserver - `turnserver -c /这个地方路径填完整/conf/turn/turnserver-with-secret-user.conf` - -固定帐号密码 : `docker/coturn/turnserver-with-fixed-user.conf` - -1. 修改 `listening-device`, `listening-ip`, `external-ip`, `user`, `realm` 几个字段即可 -2. 生成用户 - `turnadmin -a -u 帐号 -p 密码 -r 这个地方填配置文件中的relam` -3. 启动turnserver - `turnserver -c /这个地方路径填完整/docker/coturn/turnserver-with-secret-user.conf` - -部署好coturn后,在对应的 `tlrtcfile.env` 配置中设置好webrtc相关信息即可 - - ## webrtc-stun中继服务地址 - tl_rtc_file_webrtc_stun_host= - ## webrtc-turn中继服务地址 - tl_rtc_file_webrtc_turn_host= - ## webrtc中继服务用户名 - tl_rtc_file_webrtc_turn_username=tlrtcfile - ## webrtc中继服务密码 - tl_rtc_file_webrtc_turn_credential=tlrtcfile - ## webrtc中继服务Secret - tl_rtc_file_webrtc_turn_secret=tlrtcfile - ## webrtc中继服务帐号过期时间 (毫秒) - tl_rtc_file_webrtc_turn_expire=86400000 +npm run http-socket +``` -## Docker +或者使用https模式启动,访问 `https://你的机器ip:9092` + +- 启动api服务 和 socket服务 + +``` +npm run https-api + +npm run https-socket +``` + +## docker部署 目前支持 `官方镜像` 和 `自行打包镜像`,使用官方镜像目前支持两种操作方式 `docker脚本启动`,`docker-compose启动` +#### docker一键脚本启动 -和 `自行部署` 操作/配置上的差异有下面两点。 - -- [x] docker环境默认开启数据库,coturn服务 - -- [x] docker环境需要挂载coturn的配置,项目基础配置(tlrtcfile.env) - -由于是内置coturn和mysql服务,所以这两个相应的配置(可以在docker-compose.yml中找到具体配置文件位置),也需要在启动前修改好。 - -#### 使用官方镜像(docker脚本启动) : - -按需修改好 `tlrtcfile.env` 配置 (或使用默认配置也可) 后,进入 `bin/` 目录执行脚本 `auto-pull-and-start-docker.sh` +进入 `bin/` 目录执行脚本 `auto-pull-and-start-docker.sh` ``` chmod +x ./auto-pull-and-start-docker.sh + ./auto-pull-and-start-docker.sh ``` +#### docker-compose启动 -#### 使用官方镜像(docker-compose启动) : +根据你的 `Docker Compose` 版本在 `主目录` 执行如下对应的命令 -按需修改好 `tlrtcfile.env` 配置 (或使用默认配置也可) 后,根据你的`Docker Compose`版本在主目录执行如下对应的命令 - -- 对于`Docker Compose V1` +- 对于 `Docker Compose V1` ``` docker-compose --profile=http up -d ``` -- 对于`Docker Compose V2` +- 对于 `Docker Compose V2` ``` docker compose --profile=http up -d ``` -#### 自行打包启动镜像(docker-compose打包启动) : +#### 自行打包启动镜像 -确认修改好 `tlrtcfile.env` 配置文件 (或使用默认配置也可) 后, 进入 `docker/` 目录后根据你的`Docker Compose`版本在主目录执行如下对应的命令 +进入 `docker/` 目录后根据你的 `Docker Compose` 版本在主目录执行如下对应的命令 -- 对于`Docker Compose V1` +- 对于 `Docker Compose V1` ``` docker-compose -f docker-compose-build-code.yml up -d ``` -- 对于`Docker Compose V2` +- 对于 `Docker Compose V2` ``` docker compose -f docker-compose-build-code.yml up -d ``` @@ -191,32 +144,36 @@ docker compose -f docker-compose-build-code.yml up -d 下载项目后,可以进入 `bin/` 目录,选择对应的系统脚本,直接执行即可,会自动检测安装环境,自动安装依赖,自动启动服务 -**注意 : 执行之前可以先修改好 tlrtcfile.env 配置,如使用默认配置,后续修改需要重启两个服务才能生效**,重启可以先执行 `停止服务脚本`,然后再次执行 `自动脚本` 即可 - #### ubuntu自动脚本 (比如ubuntu16) +- 如果脚本没有执行权限,执行一下下面的命令 ``` chmod +x ./ubuntu16/*.sh +``` -cd ubuntu16/ - +- 使用 `http` 方式则是执行这个脚本 +``` ./auto-check-install-http.sh ``` -使用https方式则是执行这个脚本 + +- 或者使用 `https` 方式则是执行这个脚本 ``` ./auto-check-install-https.sh ``` -停止服务脚本 : + +- 停止服务脚本 : ``` ./auto-stop.sh ``` #### windows自动脚本 +- 使用 `http` 方式则是执行这个脚本 ``` windows/auto-check-install-http.bat ``` -或者使用https方式则是执行这个脚本 + +- 或者使用https方式则是执行这个脚本 ``` windows/auto-check-install-https.bat ``` @@ -226,6 +183,68 @@ windows/auto-check-install-https.bat [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/898TLE?referralCode=iamtsm) +## 其他配置项 +#### 配置数据库 (非必须步骤) + +需要自行安装mysql数据库,新建一个数据库名称为 `webchat`,然后修改 `tlrtcfile.env` 中的数据库相关配置即可 + +#### 企微通知 (非必须步骤) + +如果需要设置一些访问通知,错误告警通知,可以在企业微信建立机器人后,每一个机器人会有一个key,修改 `tlrtcfile.env` 中的企业微信通知相关配置即可 + +#### OSS云存储 (非必须步骤) + +目前支持对接了seafile存储,后续会逐步支持阿里云,腾讯云,七牛云,自己的服务器等存储方式。 修改 `tlrtcfile.env` 中的OSS存储相关配置即可 + +#### Chat-GPT (非必须步骤) + +对接了openai的接口,内置了一个聊天对话框, 修改 `tlrtcfile.env` 中的openai相关配置即可 + +#### 管理后台 (非必须步骤) + +前提 : 需要开启数据库配置 + +修改 `tlrtcfile.env` 中的管理后台相关配置即可, 启动后,输入配置的房间号,输入密码,即可进入管理后台 + +#### 配置turnserver (局域网非必须步骤,公网必须步骤) + +目前有两种形式去生成使用turn服务的帐号密码,一种是固定帐号密码 (优先推荐),一种是有效期帐号密码。**选一种方式即可** ,以下以ubuntu示例 + +安装coturn + +``` +sudo apt-get install coturn +``` + +有效帐号密码模式配置文件 : `docker/coturn/turnserver-with-secret-user.conf` + +- 修改配置文件字段 +``` +`listening-device`, `listening-ip`, `external-ip`, `static-auth-secret`, `realm` +``` +- 启动turnserver + +``` +turnserver -c /这个地方路径填完整/conf/turn/turnserver-with-secret-user.conf +``` + +固定帐号密码模式配置文件 : `docker/coturn/turnserver-with-fixed-user.conf` + +- 修改配置文件字段 +``` +`listening-device`, `listening-ip`, `external-ip`, `user`, `realm` +``` +- 生成用户 +``` +turnadmin -a -u 帐号 -p 密码 -r 这个地方填配置文件中的relam +``` +- 启动turnserver +``` +turnserver -c /这个地方路径填完整/docker/coturn/turnserver-with-secret-user.conf +``` + +部署好coturn后,在对应的 `tlrtcfile.env` 配置中设置好webrtc相关信息即可 + ## 概述图 ![image](doc/tl-rtc-file-tool.jpg) diff --git a/doc/README_EN.md b/doc/README_EN.md index 9056264..5c69734 100644 --- a/doc/README_EN.md +++ b/doc/README_EN.md @@ -1,4 +1,4 @@ -# tl-rtc-file-tool【Beyond File Transfer, Beyond Imagination】 +# tl-rtc-file-tool (tl webrtc file tools) [![](https://img.shields.io/badge/webrtc-p2p-blue)](https://webrtc.org.cn/) [![](https://img.shields.io/badge/code-simple-green)](https://github.com/iamtsm/tl-rtc-file/) @@ -7,283 +7,232 @@ [![](https://img.shields.io/badge/platform-unlimited-coral)](https://github.com/iamtsm/tl-rtc-file/)

-Experience | +DemoDockerHub | -EN-DOC +EN-DOC | QQ Group: +624214498

-

QQ Group: 624214498

- ## Table of Contents - [Background](#background) -- [Introduction](#introduction) - [Advantages](#advantages) -- [Extensions](#extensions) -- [Preparation (Essential Steps)](#preparation-essential-steps) -- [Configure Websocket (Essential Steps)](#configure-websocket-essential-steps) -- [Startup (Essential Steps)](#startup-essential-steps) -- [Configure Database (Non-Essential Steps)](#configure-database-non-essential-steps) -- [Admin Panel (Non-Essential Steps)](#admin-panel-non-essential-steps) -- [WeChat Work Notification (Non-Essential Steps)](#wechat-work-notification-non-essential-steps) -- [OSS Cloud Storage (Non-Essential Steps)](#oss-cloud-storage-non-essential-steps) -- [Chat-GPT (Non-Essential Steps)](#chat-gpt-non-essential-steps) -- [Configure Turn Server (LAN Non-Essential Steps, Internet Essential Steps)](#configure-turn-server-lan-non-essential-steps-internet-essential-steps) -- [Docker](#docker) +- [Pre-deployment Considerations](#pre-deployment-considerations) +- [Self-Deployment](#self-deployment) + - [Installing Dependencies](#installing-dependencies) + - [Starting the Service](#starting-the-service) +- [Docker Deployment](#docker-deployment) + - [One-Click Docker Script](#one-click-docker-script) + - [Using docker-compose](#using-docker-compose) + - [Self-Building and Starting the Image](#self-building-and-starting-the-image) - [Other Deployment Methods](#other-deployment-methods) +- [Configuring the Database (Optional)](#configuring-the-database-optional) +- [Admin Panel (Optional)](#admin-panel-optional) +- [WeChat Notifications (Optional)](#wechat-notifications-optional) +- [OSS Cloud Storage (Optional)](#oss-cloud-storage-optional) +- [Chat-GPT (Optional)](#chat-gpt-optional) +- [Configuring turnserver (Optional for LAN, Required for WAN)](#configuring-turnserver-optional-for-lan-required-for-wan) - [Overview Diagram](#overview-diagram) - [License](#license) - [Disclaimer](#disclaimer) -#### Background: Consolidated from the topic of the 20th-year graduation project +## Background -#### Introduction: (tl webrtc datachannel filetools) Transferring files on the web using WebRTC, supporting the transfer of very large files. +This project was developed based on the topic of the graduation project in 2020. It allows file transfer using WebRTC in web applications and supports transferring large files. -#### Advantages: Fragmented transmission, cross-device, cross-platform, easy to use, unlimited speed within the intranet (up to over 70MB/s in the LAN), supports private deployment, supports multi-file drag-and-drop sending, web file preview. +## Advantages -#### Extensions: Extends many rich features, such as local screen recording, remote screen sharing (no delay), remote audio and video calls (no delay), live streaming (no delay), password-protected rooms, OSS cloud storage, relay service settings, WebRTC detection, WebRTC statistics, text transmission (group chat, private chat), public chat, remote whiteboard, AI chatbox, rich backend management, real-time execution log display, robot alert notifications, and more... +Fragmented transmission, cross-platform, platform-independent, easy to use, no speed limit in the local network (up to over 70 MB/s in the LAN), supports private deployment, supports drag-and-drop sending of multiple files, web file preview. Many additional features have been added, such as local screen recording, remote screen sharing (zero-latency), remote audio and video calls (zero-latency), live streaming (zero-latency), password-protected rooms, OSS cloud storage, relay service settings, WebRTC detection, WebRTC statistics, text transmission (group chat, private chat), public chat, remote whiteboard, AI chatbox, feature-rich admin panel, real-time execution log display, robot alert notifications, and more. -## Preparation (Essential Steps) +## Pre-deployment Considerations -1. Install node-14.21.x or higher and npm, then run the following command in the project directory: +Whether it's self-deployment, Docker deployment, or other script deployments, you need to modify the corresponding configurations in `tlrtcfile.env` before performing the following operations. Further configuration modifications and service restarts are required. + +Of course, you can also use the default configurations without modifications, but the default configurations are only suitable for testing on localhost. They won't be accessible to others, making it impossible for others to use. Therefore, if you intend to deploy on a server for local network or public network users, you must configure `tlrtcfile.env` accordingly. + +## Self-Deployment +#### Installing Dependencies + +Install Node.js 14.21.x or above, and npm. Then, navigate to the project directory and run the following command: ``` cd svr/ + npm install ``` -2. For the first run or self-development of the page, use either of the following commands: +For the first run, execute the following command: +``` +npm run build:pro +``` +If you need to develop or modify the frontend pages, use this command. If not, you can skip this step: +``` +npm run build:dev +``` -`npm run build:dev` (Use this command if you need to develop/modifty the frontend page) -`npm run build:pro` (Use this command if you don't need to develop/modifty the frontend page) +#### Starting the Service -3. Modify the `tlrtcfile.env` configuration file. +Start the following two services. Choose one mode to start. The only difference between them is that the HTTPS mode is required to use features like audio/video streaming, live streaming, and screen sharing. Other features are not affected. -## Configure Websocket (Essential Steps) +After starting in HTTP mode, access the service at `http://your_machine_ip:9092`. -Modify the corresponding websocket configurations in `tlrtcfile.env`: +- Start the API and socket services: +``` +npm run http-api +npm run http-socket +``` - ## Websocket server port - tl_rtc_file_socket_port=8444 +Or, start in HTTPS mode and access the service at `https://your_machine_ip:9092`. - ## Websocket server address - ## "domain or ip:port or domain:port" - tl_rtc_file_socket_host=127.0.0.1 +- Start the API and socket services: +``` +npm run https-api +npm run https-socket +``` -## Startup (Essential Steps) +## Docker Deployment -Start the following two services. Choose one mode to start, and the difference between them is that the HTTPS environment is required for audio, video, live streaming, and screen sharing features. Other features are not affected. +Currently, both `official images` and `self-built images` are supported. For official images, there are two ways to operate: `docker script startup` and `docker-compose startup`. -After starting in HTTP mode, access http://your_machine_ip:9092. - -- API service: `npm run http-api` -- Socket service: `npm run http-socket` - -After starting in HTTPS mode, access https://your_machine_ip:9092. - -- API service: `npm run https-api` -- Socket service: `npm run https-socket` - -## Configure Database (Non-Essential Steps) - -Modify the database-related configurations in `tlrtcfile.env`: - - ## Enable database - tl_rtc_file_db_open=false - ## Database address - tl_rtc_file_db_mysql_host=mysql - ## Database port - tl_rtc_file_db_mysql_port=3306 - ## Database name - tl_rtc_file_db_mysql_dbName=webchat - ## Database username - tl_rtc_file_db_mysql_user=tlrtcfile - ## Database password - tl_rtc_file_db_mysql_password=tlrtcfile - -## Admin Panel (Non-Essential Steps) - -Prerequisite: Database configuration must be enabled. - -Modify the admin panel-related configurations in `tlrtcfile.env`. After starting, enter the configured room number and password to access the admin panel: - - ## Admin panel room number - tl_rtc_file_manage_room=tlrtcfile - ## Admin panel password - tl_rtc_file_manage_password=tlrtcfile - -## WeChat Work Notification (Non-Essential Steps) - -Modify the WeChat Work notification-related configurations in `tlrtcfile.env`: - - ## WeChat Work notification switch - tl_rtc_file_notify_open=false - ## WeChat Work notification robot KEY, normal notifications, comma-separated if multiple keys - tl_rtc_file_notify_qiwei_normal= - ## WeChat Work notification robot KEY, error notifications, comma-separated if multiple keys - tl_rtc_file_notify_qiwei_error= - -## OSS Cloud Storage (Non-Essential Steps) - -Modify the OSS storage-related configurations in `tlrtcfile.env`: - - ## oss-seafile storage repository ID - tl_rtc_file_oss_seafile_repoid= - ## oss-seafile address - tl_rtc_file_oss_seafile_host= - ## oss-seafile username - tl_rtc_file_oss_seafile_username= - ## oss-seafile password - tl_rtc_file_oss_seafile_password= - - ## - - oss-alyun storage accessKey - tl_rtc_file_oss_alyun_AccessKey= - ## oss-aly storage SecretKey - tl_rtc_file_oss_alyun_Secretkey= - ## oss-aly storage bucket - tl_rtc_file_oss_alyun_bucket= - - ## oss-txyun storage accessKey - tl_rtc_file_oss_txyun_AccessKey= - ## oss-txyun storage SecretKey - tl_rtc_file_oss_txyun_Secretkey= - ## oss-txyun storage bucket - tl_rtc_file_oss_txyun_bucket= - - ## oss-qiniuyun storage accessKey - tl_rtc_file_oss_qiniuyun_AccessKey= - ## oss-qiniuyun storage SecretKey - tl_rtc_file_oss_qiniuyun_Secretkey== - ## oss-qiniuyun storage bucket - tl_rtc_file_oss_qiniuyun_bucket= - -## Chat-GPT (Non-Essential Steps) - -Modify the OpenAI-related configurations in `tlrtcfile.env`: - - ## openai-key, comma-separated if multiple keys - tl_rtc_file_openai_keys= - -## Configure Turn Server (LAN Non-Essential Steps, Internet Essential Steps) - -Currently, there are two ways to generate and use Turn server accounts and passwords: fixed accounts and passwords (recommended), and time-limited accounts and passwords. **Choose one method**: - -Example for Ubuntu: - -- Install coturn: `sudo apt-get install coturn` - -Valid accounts and passwords: `docker/coturn/turnserver-with-secret-user.conf` - -1. Modify the fields `listening-device`, `listening-ip`, `external-ip`, `static-auth-secret`, and `realm`. -2. Start turnserver: - `turnserver -c /path/to/complete/conf/turn/turnserver-with-secret-user.conf` - -Fixed accounts and passwords: `docker/coturn/turnserver-with-fixed-user.conf` - -1. Modify the fields `listening-device`, `listening-ip`, `external-ip`, `user`, and `realm`. -2. Generate users: - `turnadmin -a -u username -p password -r realm_from_config_file` -3. Start turnserver: - `turnserver -c /path/to/complete/docker/coturn/turnserver-with-secret-user.conf` - -After deploying coturn, set up WebRTC-related information in the corresponding `tlrtcfile.env` configuration: - - ## webrtc-stun relay service address - tl_rtc_file_webrtc_stun_host= - ## webrtc-turn relay service address - tl_rtc_file_webrtc_turn_host= - ## webrtc relay service username - tl_rtc_file_webrtc_turn_username=tlrtcfile - ## webrtc relay service password - tl_rtc_file_webrtc_turn_credential=tlrtcfile - ## webrtc relay service secret - tl_rtc_file_webrtc_turn_secret=tlrtcfile - ## webrtc relay service account expiration time (milliseconds) - tl_rtc_file_webrtc_turn_expire=86400000 - -## Docker - -Currently, support is provided for `official images` and `self-packaged images`. Using official images supports two methods: `docker script startup` and `docker-compose startup`. - -Unlike self-deployment on a server/computer, the Docker environment by default starts the database and coturn services, requiring minimal additional steps for setup. - -### Using Official Images (Docker Script Startup): - -After modifying the `tlrtcfile.env` configuration as needed (or using the default configuration), navigate to the `bin/` directory and execute the `auto-pull-and-start-docker.sh` script: +#### One-Click Docker Script +Navigate to the `bin/` directory and execute the `auto-pull-and-start-docker.sh` script: ``` chmod +x ./auto-pull-and-start-docker.sh ./auto-pull-and-start-docker.sh ``` -### Using Official Images (Docker-Compose Startup): +#### Using docker-compose -After modifying the `tlrtcfile.env` configuration as needed (or using the default configuration), execute the following command based on your `Docker Compose` version in the main directory: +In the main directory, execute the corresponding command based on your Docker Compose version: -- For `Docker Compose V1`: +- For Docker Compose V1: ``` docker-compose --profile=http up -d ``` -- For `Docker Compose V2`: +- For Docker Compose V2: ``` docker compose --profile=http up -d ``` -### Self-Packaged Image (Docker-Compose Self-Packaged Startup): +#### Self-Building and Starting the Image -After confirming the modification of the `tlrtcfile.env` configuration file (or using the default configuration), navigate to the `docker/` directory and execute the following command based on your `Docker Compose` version in the main directory: +Navigate to the `docker/` directory and execute the corresponding command based on your Docker Compose version: -- For `Docker Compose V1`: +- For Docker Compose V1: ``` docker-compose -f docker-compose-build-code.yml up -d ``` -- For `Docker Compose V2`: +- For Docker Compose V2: ``` docker compose -f docker-compose-build-code.yml up -d ``` ## Other Deployment Methods -In addition to the above manual installation, Docker official images, and self-packaged images, there are also options for automatic scripts and one-click deployment on hosting platforms. +In addition to the manual installation, Docker official images, and self-built Docker images, there are other methods such as automatic scripts and one-click deployments on hosting platforms. -After downloading the project, navigate to the `bin/` directory and select the appropriate system script to execute. The script will automatically detect the installation environment, install dependencies, and start the services automatically. +After downloading the project, navigate to the `bin/` directory and choose the appropriate system script to execute. It will automatically detect the environment, install dependencies, and start the service. -**Note: Before executing the script, you can modify the configuration first. If you're using the default configuration, you'll need to restart both services for the changes to take effect. To do this, execute the `Stop Services` script and then run the `Automatic Script` again.** - -### Ubuntu Automatic Script (e.g., ubuntu16): +#### Automatic script for Ubuntu (e.g., Ubuntu 16) +- If the script doesn't have execution permission, run the following command: ``` chmod +x ./ubuntu16/*.sh +``` -cd ubuntu16/ - +- If using HTTP, execute this script: +``` ./auto-check-install-http.sh ``` -For HTTPS, use this script: + +- If using HTTPS, execute this script: ``` ./auto-check-install-https.sh ``` -Stop Services Script: + +- To stop the service: ``` ./auto-stop.sh ``` -### Windows Automatic Script: +#### Automatic script for Windows +- If using HTTP, execute this script: ``` windows/auto-check-install-http.bat ``` -For HTTPS, use this script: + +- If using HTTPS, execute this script: ``` windows/auto-check-install-https.bat ``` -### Zeabur One-Click Deployment Platform +#### One-Click Deployment on Zeabur Platform [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/898TLE?referralCode=iamtsm) +## Other Configuration Options + +#### Configuring the Database (Optional) + +You need to install MySQL database manually, create a database named `webchat`, and then modify the database-related configurations in `tlrtcfile.env`. + +#### Admin Panel (Optional) + +Prerequisite: Database configuration must be enabled. + +Modify the admin panel-related configurations in `tlrtcfile.env`. After starting, enter the configured room number and password to access the admin panel. + +#### WeChat Notifications (Optional) + +If you need to set up notification for access and error alerts, you can create a WeChat Work robot and get an API key. Modify the WeChat notification configurations in `tlrtcfile.env`. + +#### OSS Cloud Storage (Optional) + +The project currently supports Seafile storage integration, and future updates will include support for Alibaba Cloud, Tencent Cloud, Qiniu Cloud, and self-hosted server storage methods. Modify the OSS storage configurations in `tlrtcfile.env`. + +#### Chat-GPT (Optional) + +Integrated with the OpenAI API, this project includes a chat dialog. Modify the OpenAI configurations in `tlrtcfile.env`. + +#### Configuring turnserver (Optional for LAN, Required for WAN) + +There are two ways to generate TURN server credentials: fixed credentials (recommended) and time-limited credentials. Choose one method. The following example uses Ubuntu. + +Install coturn: +``` +sudo apt-get install coturn +``` + +For time-limited credentials, modify the configuration file `docker/coturn/turnserver-with-secret-user.conf`. + +- Modify the fields in the configuration file: +``` +`listening-device`, `listening-ip`, `external-ip`, `static-auth-secret`, `realm` +``` +- Start the turnserver: +``` +turnserver -c /path/to/conf/turn/turnserver-with-secret-user.conf +``` + +For fixed credentials, modify the configuration file `docker/coturn/turnserver-with-fixed-user.conf`. + +- Modify the fields in the configuration file: +``` +`listening-device`, `listening-ip`, `external-ip`, `user`, `realm` +``` +- Generate a user: +``` +turnadmin -a -u username -p password -r realm_in_config_file +``` +- Start the turnserver: +``` +turnserver -c /path/to/docker/coturn/turnserver-with-secret-user.conf +``` + +After setting up coturn, configure the WebRTC-related information in the corresponding `tlrtcfile.env` configuration. + ## Overview Diagram ![image](tl-rtc-file-tool.jpg) diff --git a/svr/conf/cfg.json b/svr/conf/cfg.json index 466706a..fa6aa29 100644 --- a/svr/conf/cfg.json +++ b/svr/conf/cfg.json @@ -1,5 +1,5 @@ { - "version": "10.4.2", + "version": "10.4.3", "socket": { "port": "请到 tlrtcfile.env 中进行配置", "host": "请到 tlrtcfile.env 中进行配置" diff --git a/svr/res/css/index.css b/svr/res/css/index.css index 2331b95..79ea321 100644 --- a/svr/res/css/index.css +++ b/svr/res/css/index.css @@ -1203,4 +1203,19 @@ body { /** 765px以上 */ @media screen and (min-width: 765px) { +} + +/** 语音连麦动画覆盖 */ +@keyframes layui-scale-spring { + 0% { + transform: scale(.3); + } + 80% { + opacity: .8; + transform: scale(0.8); + } + 100% { + opacity: 0; + transform: scale(0.6); + } } \ No newline at end of file diff --git a/svr/res/index.html b/svr/res/index.html index 9990c31..73cf179 100644 --- a/svr/res/index.html +++ b/svr/res/index.html @@ -98,6 +98,10 @@ v-show="isLiveShare && owner"> {{lang.living}}: {{liveShareTimes < 10 ? '0' + liveShareTimes : liveShareTimes}}{{lang.second}} + + {{lang.audioing}}: {{audioShareTimes < 10 ? '0' + audioShareTimes : + audioShareTimes}}{{lang.second}} @@ -208,7 +212,10 @@ {{lang.chat_comm}} + style="right: 2px; top: 2px; width: 7px; height: 7px;position: relative;"> + +
{{lang.start_live}}
+
+
+ + {{lang.start_audio}} +
+
+ + {{lang.start_audio}} +
+
@@ -315,8 +337,8 @@
- 【{{lang.owner}}】- - 【{{lang.self}}】- {{nickName}} + 【{{roomTypeName}}】- + 【{{lang.owner}}】-【{{lang.self}}】- {{nickName}} {{socketId}} @@ -1137,6 +1159,51 @@
+ + +
+
+
+
+ {{lang.audio_sharing}} +
+
+
+
+
+
+
+ + + +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ @@ -1144,6 +1211,7 @@ + diff --git a/svr/res/js/audioShare.js b/svr/res/js/audioShare.js new file mode 100644 index 0000000..c2ca30c --- /dev/null +++ b/svr/res/js/audioShare.js @@ -0,0 +1,192 @@ +// --------------------------- // +// -- audioShare.js -- // +// -- version : 1.0.0 -- // +// -- date : 2023-08-15 -- // +// --------------------------- // + +var audioShare = new Vue({ + el: '#audioShareApp', + data: function () { + return { + stream: null, + times: 0, + interverlId: 0, + track: null, + } + }, + methods: { + getMediaPlay: function (constraints) { + let media = null; + let defaultConstraints = { + // 音频轨道 + audio:true, + // 视频轨道 + video: false + }; + if(constraints){ + defaultConstraints = constraints + } + if(window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia){ + media = window.navigator.mediaDevices.getUserMedia(defaultConstraints); + } else if (window.navigator.mozGetUserMedia) { + media = navagator.mozGetUserMedia(defaultConstraints); + } else if (window.navigator.getUserMedia) { + media = window.navigator.getUserMedia(defaultConstraints) + } else if (window.navigator.webkitGetUserMedia) { + media = new Promise((resolve, reject) => { + window.navigator.webkitGetUserMedia(defaultConstraints, (res) => { + resolve(res) + }, (err) => { + reject(err) + }); + }) + } + return media + }, + startAudioShare: async function (callback) { + let that = this; + + let msgData = { + "Requested device not found" : "没有检测到麦克风" + } + let msg = "获取设备权限失败"; + + if (this.stream == null) { + try { + this.stream = await this.getMediaPlay(); + } catch (error) { + console.log(error) + msg = msgData[error.message] + } + } + + if (this.stream == null) { + if (window.layer) { + layer.msg("获取设备权限失败") + } + window.Bus.$emit("changeAudioShareState", false) + callback && callback() + return; + } + + const video = document.querySelector("#selfMediaShareAudio"); + video.addEventListener('loadedmetadata', function() { + window.Bus.$emit("addSysLogs", "loadedmetadata") + // ios 微信浏览器兼容问题 + video.play(); + document.addEventListener('WeixinJSBridgeReady', function () { + window.Bus.$emit("addSysLogs", "loadedmetadata WeixinJSBridgeReady") + video.play(); + }, false); + }); + document.addEventListener('WeixinJSBridgeReady', function () { + window.Bus.$emit("addSysLogs", "WeixinJSBridgeReady") + video.play(); + }, false); + video.srcObject = this.stream; + video.play(); + + //计算时间 + this.interverlId = setInterval(() => { + that.times += 1; + window.Bus.$emit("changeAudioShareTimes", that.times) + + $("#audioShareIcon").css("color", "#fb0404") + $("#audioShareTimes").css("color", "#fb0404") + setTimeout(() => { + $("#audioShareIcon").css("color", "#000000") + $("#audioShareTimes").css("color", "#000000") + }, 500) + }, 1000); + + if (window.layer) { + layer.msg("开始语音连麦,再次点击按钮即可挂断") + } + + this.stream.getTracks().forEach(function (track) { + that.track = track; + callback && callback(track, that.stream) + }); + }, + stopAudioShare: function () { + if (this.stream) { + this.stream.getTracks().forEach(track => track.stop()); + } + + clearInterval(this.interverlId); + + window.Bus.$emit("changeAudioShareTimes", 0); + + if (window.layer) { + layer.msg("语音连麦结束,本次连麦时长 " + this.times + "秒") + } + setTimeout(() => { + $("#audioShareIcon").css("color", "#000000") + }, 1000); + + this.stream = null; + this.times = 0; + + return; + }, + changeAudioShareDevice: async function ({kind, rtcConnect}) { + //重新获取流 + let newStream = null; + try{ + newStream = await this.getMediaPlay(); + }catch(e){ + console.log("changeAudioShareMediaTrackAndStream error! ", e) + } + + //获取流/权限失败 + if(newStream === null){ + callback && callback(false) + return; + } + + if(kind === 'audio'){ + newStream.getAudioTracks()[0].enabled = true; + if(rtcConns){//远程track替换 + for(let id in rtcConns){ + const senders = rtcConns[id].getSenders(); + const sender = senders.find((sender) => (sender.track ? sender.track.kind === 'audio' : false)); + if(!sender){ + console.error("changeDevice find sender error! "); + return + } + sender.replaceTrack(newStream.getAudioTracks()[0]); + } + } + } + + const video = document.querySelector("#selfMediaShareVideo"); + video.addEventListener('loadedmetadata', function() { + // ios 微信浏览器兼容问题 + window.Bus.$emit("addSysLogs", "loadedmetadata") + video.play(); + document.addEventListener('WeixinJSBridgeReady', function () { + window.Bus.$emit("addSysLogs", "loadedmetadata WeixinJSBridgeReady") + video.play(); + }, false); + }); + document.addEventListener('WeixinJSBridgeReady', function () { + window.Bus.$emit("addSysLogs", "WeixinJSBridgeReady") + video.play(); + }, false); + + //替换本地音频流 + this.stream = new MediaStream([this.stream.getAudioTracks()[0]]); + video.srcObject = this.stream; + video.play(); + }, + getAudioShareTrackAndStream: function (callback) { + callback(this.track, this.stream) + }, + }, + mounted: async function () { + window.Bus.$on("startAudioShare", this.startAudioShare); + window.Bus.$on("stopAudioShare", this.stopAudioShare); + window.Bus.$on("getAudioShareTrackAndStream", this.getAudioShareTrackAndStream); + window.Bus.$on("changeAudioShareDevice", this.changeAudioShareDevice); + } +}) \ No newline at end of file diff --git a/svr/res/js/comm.js b/svr/res/js/comm.js index 12595b5..4c1e695 100644 --- a/svr/res/js/comm.js +++ b/svr/res/js/comm.js @@ -482,6 +482,23 @@ window.tlrtcfile = { window.addEventListener('keyup', function (event) { callback("keyup", event) }) + }, + getRoomTypeZh: function (type){ + if(type === 'file'){ + return "文件房间" + }else if(type === 'live'){ + return "直播房间" + }else if(type === 'video'){ + return "音视频房间" + }else if(type === 'screen'){ + return "屏幕共享房间" + }else if(type === 'password'){ + return "密码房间" + }else if(type === 'audio'){ + return "语音连麦房间" + }else{ + return "未知类型房间" + } }, scrollToBottom: function (dom, duration, timeout) { let start = dom.scrollTop; diff --git a/svr/res/js/index.js b/svr/res/js/index.js index 42bc0fc..776ee0e 100644 --- a/svr/res/js/index.js +++ b/svr/res/js/index.js @@ -46,6 +46,8 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { videoShareTimes: 0, //当前音视频时间 isLiveShare: false, //是否在直播中 liveShareTimes: 0, //当前直播时间 + isAudioShare: false, //是否在语音连麦中 + audioShareTimes: 0, //当前语音连麦时间 isPasswordRoom: false, //是否在密码房中 isAiAnswering: false, //是否ai正在回答中 switchDataGet: false, // 是否已经拿到配置开关数据 @@ -77,6 +79,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { mediaVideoMaskHeightNum: -150, // 用于控制音视频展示 mediaScreenMaskHeightNum: -150, // 用于控制屏幕共享展示 mediaLiveMaskHeightNum: -150, // 用于控制直播展示 + mediaAudioMaskHeightNum: -150, // 用于控制语音连麦展示 logsHeight: 0, // 日志栏目展示高度 sendFileRecoderHeight : 0, // 发送文件展示列表高度 @@ -190,6 +193,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { } } }, + roomTypeName: function(){ + return window.tlrtcfile.getRoomTypeZh(this.roomType) + } }, watch: { isAiAnswering: function (newV, oldV) { @@ -290,6 +296,12 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { resolve(stream) }); }); + }else if(type === 'audio'){ + stream = await new Promise((resolve, reject) => { + stream = window.Bus.$emit("getAudioShareTrackAndStream", (track, stream) => { + resolve(stream) + }); + }); }else if(type === 'live'){ stream = await new Promise((resolve, reject) => { stream = window.Bus.$emit("getLiveShareTrackAndStream", (track, stream) => { @@ -1379,6 +1391,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { this.addUserLogs(this.lang.in_living) return } + if (this.isAudioShare) { + layer.msg(this.lang.in_audioing) + this.addUserLogs(this.lang.in_audioing) + return + } if (this.isVideoShare) { window.Bus.$emit("stopVideoShare") this.isVideoShare = !this.isVideoShare; @@ -1438,6 +1455,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { this.addUserLogs(this.lang.in_living) return } + if (this.isAudioShare) { + layer.msg(this.lang.in_audioing) + this.addUserLogs(this.lang.in_audioing) + return + } if (this.isScreenShare) { window.Bus.$emit("stopScreenShare") this.isScreenShare = !this.isScreenShare; @@ -1497,6 +1519,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { this.addUserLogs(this.lang.in_sharing_screen) return } + if (this.isAudioShare) { + layer.msg(this.lang.in_audioing) + this.addUserLogs(this.lang.in_audioing) + return + } if (this.isLiveShare) { window.Bus.$emit("stopLiveShare") this.isLiveShare = !this.isLiveShare; @@ -1569,6 +1596,70 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); } }, + // 创建/加入语音连麦房间 + startAudioShare: function () { + if (!this.switchData.openAudioShare) { + layer.msg(this.lang.feature_close) + this.addUserLogs(this.lang.feature_close) + return + } + if (this.isVideoShare) { + layer.msg(this.lang.in_videoing) + this.addUserLogs(this.lang.in_videoing) + return + } + if (this.isLiveShare) { + layer.msg(this.lang.in_living) + this.addUserLogs(this.lang.in_living) + return + } + if (this.isScreenShare) { + layer.msg(this.lang.in_living) + this.addUserLogs(this.lang.in_living) + return + } + if (this.isAudioShare) { + window.Bus.$emit("stopAudioShare") + this.isAudioShare = !this.isAudioShare; + this.addUserLogs(this.lang.end_audio_sharing); + return + } + if (this.isJoined) { + layer.msg(this.lang.please_exit_then_join_screen) + this.addUserLogs(this.lang.please_exit_then_join_screen) + return + } + let that = this; + if(that.isShareJoin){ //分享进入 + that.createMediaRoom("audio"); + that.socket.emit('message', { + emitType: "startAudioShare", + room: that.roomId, + to : that.socketId + }); + that.clickMediaAudio(); + that.isAudioShare = !that.isAudioShare; + that.addUserLogs(that.lang.start_audio_sharing); + }else{ + layer.prompt({ + formType: 0, + title: this.lang.please_enter_audio_sharing_room_num, + }, function (value, index, elem) { + that.roomId = value; + that.createMediaRoom("audio"); + layer.close(index) + + that.socket.emit('message', { + emitType: "startAudioShare", + room: that.roomId, + to : that.socketId + }); + that.clickMediaAudio(); + that.isAudioShare = !that.isAudioShare; + that.addUserLogs(that.lang.start_audio_sharing); + }); + } + }, // 打开画笔 openRemoteDraw : function(){ let that = this; @@ -2128,13 +2219,15 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { layer.close(index) that.openRoomInput = true; that.isShareJoin = true; - if(typeArgs && ['screen','live','video'].includes(typeArgs)){ + if(typeArgs && ['screen','live','video','audio'].includes(typeArgs)){ if(typeArgs === 'screen'){ that.startScreenShare(); }else if(typeArgs === 'live'){ that.startLiveShare(); }else if(typeArgs === 'video'){ that.startVideoShare(); + }else if(typeArgs === 'audio'){ + that.startAudioShare(); } }else{ that.createFileRoom(); @@ -2299,6 +2392,23 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { document.getElementById("iamtsm").style.marginLeft = "0"; } }, + clickMediaAudio : function(){ + this.showMedia = !this.showMedia; + this.touchResize(); + if (this.showMedia) { + this.addUserLogs(this.lang.expand_audio); + if(this.clientWidth < 500){ + document.getElementById("iamtsm").style.marginLeft = "0"; + }else{ + document.getElementById("iamtsm").style.marginLeft = "50%"; + } + this.mediaAudioMaskHeightNum = 0; + } else { + this.addUserLogs(this.lang.collapse_audio); + this.mediaAudioMaskHeightNum = -150; + document.getElementById("iamtsm").style.marginLeft = "0"; + } + }, typeInArr: function(arr, type, name = ""){ if(type === ''){ let fileTail = name.split(".").pop() @@ -2563,11 +2673,36 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { //远程媒体流处理 mediaTrackHandler: function(event, id){ let that = this; + + let video = null; + if(event.track.kind === 'audio'){ - return; + // audio-track事件,除了语音连麦房间之外,其他都可以跳过,因为音视频/直播/屏幕共享他们的音频流都并入了video-track了 + if(that.roomType !== 'audio'){ + return + } + + //连麦房间,只有音频数据 + $(`#mediaAudioRoomList`).append(` +
+ + + + + +
+ `); + video = document.querySelector(`#otherMediaAudioShare${id}`); } - let video = null; if(this.roomType === 'video'){ $(`#mediaVideoRoomList`).append(`
@@ -3088,14 +3223,14 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { return item.id !== id; }) - if(['live','video','screen'].includes(this.roomType)){ + if(['live','video','screen','audio'].includes(this.roomType)){ //主播异常关闭直播,观众页面强制刷新 if(this.roomType === 'live' && removeIsOwner){ window.location.reload() } //多人音视频/多人屏幕共享,有人异常退出,移除对应的video标签 - if(this.roomType === 'video' || this.roomType === 'screen'){ + if(this.roomType === 'video' || this.roomType === 'screen' || this.roomType === 'audio'){ $(`#otherMediaVideoShare${id}`).parent().remove(); } } @@ -3189,6 +3324,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { if(data.type === 'video'){ window.Bus.$emit("startVideoShare", that.videoConstraints); } + if(data.type === 'audio'){ + window.Bus.$emit("startAudioShare"); + } if(data.type === 'live'){ window.Bus.$emit("startLiveShare", { liveShareMode : that.liveShareMode @@ -3213,7 +3351,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { await new Promise(resolve => { if(data.type === 'screen'){ - window.Bus.$emit("startScreenShare",(track, stream) => { + window.Bus.$emit("startScreenShare", (track, stream) => { //其他人将数据流添加到通道中, 此时需要addTrack,因为后面会有offer收集,然后进行answer ...等后续操作 otherRtcConnect.addTrack(track, stream); resolve() @@ -3224,6 +3362,12 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { otherRtcConnect.addTrack(track, stream); resolve() }); + }else if(data.type === 'audio'){ + window.Bus.$emit("startAudioShare", (track, stream) => { + //其他人将数据流添加到通道中, 此时需要addTrack,因为后面会有offer收集,然后进行answer ...等后续操作 + otherRtcConnect.addTrack(track, stream); + resolve() + }); }else{ resolve(); } @@ -3272,6 +3416,14 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { rtcConnect.addTrack(track, stream); }); } + + if (data.type === 'audio') { + //比如多人语音连麦,后面加入的连接已经在created事件中addTrack了 + //所以这个地方主要是通知当前连接之前的那一些连接进行addTrack,以便于当前连接能收到 + window.Bus.$emit("getAudioShareTrackAndStream", (track, stream) => { + rtcConnect.addTrack(track, stream); + }); + } if (data.type === 'live') { //比如直播,后面加入的都是观众,所以每个观众加入的时候,都会通知一下所有人可以添加媒体流到通道了(这里就是只有房主有媒体流数据) @@ -3279,6 +3431,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { rtcConnect.addTrack(track, stream); }); } + that.addPopup({ title : that.lang.join_room, msg : data.nickName + that.lang.join_room @@ -3585,6 +3738,14 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { document.querySelector(`#otherMediaLiveShareAudioOpenSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none'; document.querySelector(`#otherMediaLiveShareAudioCloseSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block'; } + }else if(data.type === 'audio'){ + if(data.kind === 'audio'){ + document.querySelector(`#otherMediaAudioShareAudioOpenSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none'; + document.querySelector(`#otherMediaAudioShareAudioCloseSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block'; + + document.querySelector(`#otherMediaAudioShareAudioOpenAnimSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none'; + document.querySelector(`#otherMediaAudioShareAudioCloseAnimSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block'; + } } }); @@ -3610,6 +3771,15 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { } }); + //退出语音连麦 + this.socket.on('stopAudioShare', function (data) { + if (data.id === that.socketId) { + that.clickMediaAudio(); + } else { + $(`#otherMediaAudioShare${data.id}`).parent().remove(); + } + }); + //ai对话 this.socket.on('openaiAnswer', function (data) { that.isAiAnswering = false @@ -3863,6 +4033,24 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { } this.liveShareTimes = res }) + window.Bus.$on("changeAudioShareTimes", (res) => { + if (res === 0) { + this.socket.emit('message', { + emitType: "stopAudioShare", + id: this.socketId, + room: this.roomId, + cost: this.audioShareTimes, + owner : this.owner, + }); + } + this.audioShareTimes = res + }) + window.Bus.$on("changeAudioShareState", (res) => { + if(!res){//状态失败,收起面板 + this.clickMediaAudio(); + } + this.isAudioShare = res + }) window.Bus.$on("sendChatingComm", (res) => { this.sendChatingComm() }) diff --git a/svr/res/js/language.js b/svr/res/js/language.js index 49f3811..2a5de0f 100644 --- a/svr/res/js/language.js +++ b/svr/res/js/language.js @@ -8,6 +8,14 @@ const local_lang = { "en": { + "audioing" : "Audioing", + "audio_sharing" : "Audio sharing", + "expand_audio" : "Expand audio panel", + "collapse_audio" : "Collapse audio panel", + "start_audio_sharing" : "Start audio sharing", + "end_audio_sharing" : "End audio sharing", + "in_audioing" : "In audioing", + "start_audio" : "Start audio", "audience" : "Audience", "webrtc_check_init" : "Webrtc check init", "webrtc_check_init_done" : "Webrtc check init done", @@ -184,6 +192,7 @@ const local_lang = { "please_enter_password": "Please enter the password room password", "please_enter_right_code": "Please enter the correct pickup code", "please_enter_room_num": "Please fill in the room number first", + "please_enter_audio_sharing_room_num" : "Please enter the audio sharing room number", "please_enter_screen_sharing_room_num": "Please enter the screen sharing room number", "please_enter_video_call_room_num": "Please enter the audio and video call room number", "please_exit_then_join_live": "Please exit the room first and then enter the live room", @@ -329,6 +338,14 @@ const local_lang = { "webrtc_ice_state" : "webrtc state" }, "zh": { + "audioing" : "语音中", + "audio_sharing" : "语音连麦", + "expand_audio" : "展开音频面板", + "collapse_audio" : "收起音频面板", + "start_audio_sharing" : "开始语音连麦", + "end_audio_sharing" : "结束语言连麦", + "in_audioing" : "正在语音中", + "start_audio" : "语音连麦", "audience" : "观众", "webrtc_check_init" : "Webrtc检测初始化", "webrtc_check_init_done" : "Webrtc检测初始化完成", @@ -515,6 +532,7 @@ const local_lang = { "please_enter_password": "请输入密码房间密码", "please_enter_right_code": "请输入正确的取件码", "please_enter_room_num": "请先填写房间号", + "please_enter_audio_sharing_room_num" : "请输入语音连麦房间号", "please_enter_screen_sharing_room_num": "请输入屏幕共享房间号", "please_enter_video_call_room_num": "请输入音视频通话房间号", "please_exit_then_join_live": "请先退出房间,然后进入直播间", diff --git a/svr/res/js/videoShare.js b/svr/res/js/videoShare.js index 3353fe3..8a9668a 100644 --- a/svr/res/js/videoShare.js +++ b/svr/res/js/videoShare.js @@ -179,7 +179,7 @@ var videoShare = new Vue({ try{ newStream = await this.getMediaPlay(constraints); }catch(e){ - console.log("changeLiveShareMediaTrackAndStream error! ", e) + console.log("changeVideoShareMediaTrackAndStream error! ", e) } //获取流/权限失败 diff --git a/svr/src/bussiness/manage/settingPage.js b/svr/src/bussiness/manage/settingPage.js index 0f382f3..b7d04a0 100644 --- a/svr/src/bussiness/manage/settingPage.js +++ b/svr/src/bussiness/manage/settingPage.js @@ -80,6 +80,11 @@ async function getSettingPageHtml(data) {
+
+
+ +
+
diff --git a/svr/src/bussiness/notify/notifyHandler.js b/svr/src/bussiness/notify/notifyHandler.js index 8fa2172..9953aa6 100644 --- a/svr/src/bussiness/notify/notifyHandler.js +++ b/svr/src/bussiness/notify/notifyHandler.js @@ -161,6 +161,35 @@ function sendStopVideoShareNotify(data) { } +/** + * 发送开始语音连麦通知 + * @param {*} data + */ +function sendStartAudioShareNotify(data) { + let notifyMsg = `## 文件传输通知 - ${data.title}` + + ` - ${data.room}\n` + + `当前时间: ${utils.formateDateTime(new Date(), "yyyy-MM-dd hh:mm:ss")}\n` + + `访问IP: ${data.ip}\n` + + `访问设备: ${data.userAgent}\n`; + notify.requestMsg(notifyMsg) +} + + +/** + * 发送停止语音连麦通知 + * @param {*} data + */ +function sendStopAudioShareNotify(data) { + let notifyMsg = `## 文件传输通知 - ${data.title}` + + ` - ${data.room}\n` + + `连麦时长: ${data.cost}秒\n` + + `当前时间: ${utils.formateDateTime(new Date(), "yyyy-MM-dd hh:mm:ss")}\n` + + `访问IP: ${data.ip}\n` + + `访问设备: ${data.userAgent}\n`; + notify.requestMsg(notifyMsg) +} + + /** * 发送开始直播通知 * @param {*} data @@ -461,6 +490,8 @@ module.exports = { sendStartVideoShareNotify, sendStartLiveShareNotify, sendStopLiveShareNotify, + sendStartAudioShareNotify, + sendStopAudioShareNotify, sendChatingRoomNotify, sendCodeFileNotify, sendFileDoneNotify, diff --git a/svr/src/controller/comm/comm.js b/svr/src/controller/comm/comm.js index a4699ed..facc97f 100644 --- a/svr/src/controller/comm/comm.js +++ b/svr/src/controller/comm/comm.js @@ -28,7 +28,7 @@ function initData(req, res) { ip = "127.0.0.1" } - let wsHost = conf.socket.host || ip + ":" + conf.socket.port; + let wsHost = conf.socket.host || ip + conf.socket.port; let data = { version : conf.version, diff --git a/svr/src/dao/room/room.js b/svr/src/dao/room/room.js index 55a5ec3..e6ca338 100644 --- a/svr/src/dao/room/room.js +++ b/svr/src/dao/room/room.js @@ -15,6 +15,7 @@ const defaultSwitchData = { openGetCodeFile: true, openVideoShare: true, openLiveShare: true, + openAudioShare: true, openRemoteDraw: true, openPasswordRoom: true, openScreenShare: true, diff --git a/svr/src/socket/rtcConstant.js b/svr/src/socket/rtcConstant.js index 6c5f232..03895da 100644 --- a/svr/src/socket/rtcConstant.js +++ b/svr/src/socket/rtcConstant.js @@ -69,6 +69,10 @@ let rtcServerMessageEvent = { startVideoShare : "startVideoShare", //结束音视频 stopVideoShare : "stopVideoShare", + //开始语音连麦 + startAudioShare : "startAudioShare", + //结束语音连麦 + stopAudioShare : "stopAudioShare", //开始直播 startLiveShare : "startLiveShare", //结束直播 diff --git a/svr/src/socket/rtcCreateJoin/createJoin.js b/svr/src/socket/rtcCreateJoin/createJoin.js index c023e7d..d72a3e9 100644 --- a/svr/src/socket/rtcCreateJoin/createJoin.js +++ b/svr/src/socket/rtcCreateJoin/createJoin.js @@ -37,7 +37,7 @@ async function userCreateAndJoin(io, socket, tables, dbClient, data){ langMode = 'zh' } - if(['file', 'screen', 'video', 'password', 'live'].indexOf(type) === -1){ + if(['file', 'screen', 'video', 'password', 'live', 'audio'].indexOf(type) === -1){ type = 'file' } @@ -114,8 +114,8 @@ async function userCreateAndJoin(io, socket, tables, dbClient, data){ return } - //流媒体房间只允许两个人同时在线 - if((type === 'screen' || type === 'video') && numClients >= 2){ + //流媒体房间只允许3个人同时在线 + if((type === 'screen' || type === 'video' || type === 'audio') && numClients >= 3){ socket.emit(rtcClientEvent.tips, { room : data.room, to : socket.id, @@ -235,6 +235,8 @@ function getRoomTypeZh(type){ return "屏幕共享" }else if(type === 'password'){ return "密码" + }else if(type === 'audio'){ + return "语音连麦" }else{ return "未知类型" } diff --git a/svr/src/socket/rtcMessage/message.js b/svr/src/socket/rtcMessage/message.js index e1db6c5..21e0f09 100644 --- a/svr/src/socket/rtcMessage/message.js +++ b/svr/src/socket/rtcMessage/message.js @@ -15,6 +15,8 @@ let rtcEventOpName = { "stopScreenShare": "停止屏幕共享", "startVideoShare": "开始音视频通话", "stopVideoShare": "停止音视频通话", + "startAudioShare": "开始语音连麦", + "stopAudioShare": "停止语音连麦", "startLiveShare": "开启直播", "stopLiveShare": "关闭直播", "startRemoteDraw": "开启远程画笔", @@ -137,6 +139,26 @@ async function message(io, socket, tables, dbClient, data){ }) } + if (emitType === rtcServerMessageEvent.startAudioShare) { + bussinessNotify.sendStartAudioShareNotify({ + title: rtcEventOpName.startAudioShare, + userAgent: userAgent, + ip: ip, + room: data.room + }) + } + + if (emitType === rtcServerMessageEvent.stopAudioShare) { + bussinessNotify.sendStopAudioShareNotify({ + title: rtcEventOpName.stopAudioShare, + userAgent: data.userAgent, + cost: data.cost, + userAgent: userAgent, + ip: ip, + room: data.room + }) + } + if (emitType === rtcServerMessageEvent.startLiveShare) { bussinessNotify.sendStartLiveShareNotify({ title: rtcEventOpName.startLiveShare, diff --git a/svr/static/layui/font-ext/demo_index.html b/svr/static/layui/font-ext/demo_index.html index 87b3aef..56dde69 100644 --- a/svr/static/layui/font-ext/demo_index.html +++ b/svr/static/layui/font-ext/demo_index.html @@ -54,6 +54,30 @@
    +
  • + +
    HOT
    +
    &#xe677;
    +
  • + +
  • + +
    热门hot
    +
    &#xe624;
    +
  • + +
  • + +
    214声波、语音消息
    +
    &#xe8c4;
    +
  • + +
  • + +
    语音
    +
    &#xe813;
    +
  • +
  • 翻转镜头
    @@ -666,9 +690,9 @@
    @font-face {
       font-family: 'iconfont';
    -  src: url('iconfont.woff2?t=1690027853800') format('woff2'),
    -       url('iconfont.woff?t=1690027853800') format('woff'),
    -       url('iconfont.ttf?t=1690027853800') format('truetype');
    +  src: url('iconfont.woff2?t=1692112288946') format('woff2'),
    +       url('iconfont.woff?t=1692112288946') format('woff'),
    +       url('iconfont.ttf?t=1692112288946') format('truetype');
     }
     

    第二步:定义使用 iconfont 的样式

    @@ -694,6 +718,42 @@
      +
    • + +
      + HOT +
      +
      .icon-rtc-file-hot1 +
      +
    • + +
    • + +
      + 热门hot +
      +
      .icon-rtc-file-remenhot +
      +
    • + +
    • + +
      + 214声波、语音消息 +
      +
      .icon-rtc-file-shengboyuyinxiaoxi +
      +
    • + +
    • + +
      + 语音 +
      +
      .icon-rtc-file-yuyin +
      +
    • +
    • @@ -1612,6 +1672,38 @@
        +
      • + +
        HOT
        +
        #icon-rtc-file-hot1
        +
      • + +
      • + +
        热门hot
        +
        #icon-rtc-file-remenhot
        +
      • + +
      • + +
        214声波、语音消息
        +
        #icon-rtc-file-shengboyuyinxiaoxi
        +
      • + +
      • + +
        语音
        +
        #icon-rtc-file-yuyin
        +
      • +
      • tB~GS+<}*_^^LpBpjFFCb%8k5$KHyG{lXFg(46S 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 delta 20800 zcmV)NK)1hutpTK_0Tg#nMn(Vu00000QzQTj00000js%euN`C_V000NHJSV}iXKZ<9 z000Dt002o2004VbctlA=Xk}q!002#x0000n0000sB}p@vXlP|&002$Q0000W0000a z2u1`FY;R*>002%D0002W0002WrmKdcZDDwD002(l z0000V0000W0V|yX#%^J4WdHzA00001KmY&&XAq-2uyAj4bN~QQKmY&&Bk6vgrX6~qq3z6To;NZ&V*NDxU#0|6Bk`;IhLIk1YEj#H^}V(HYm zK0yXnIZas^#kW|8!Sw;m#H@W+9>6MRxu<^AxtIIF+22`vA7CZWW$##b=d|TBTL1X8 z)%m=OwVltqTYvlgSM2xq1~jN4jcQDjn$b1g(0`I{>9+3az8>kZo@!a`hNiC>ZYG+k zX0DlEUTQD2ueTpQd;a1D2(KUU`f*KbwzK}`ORay9^{?=1xrX0-{O;opclon(Jb9_( zFRwq!EC(6nBCqi}Lk#lKU7KI9`l<}{!1srPFKyBP2;Zebl8Il~;A zSbxP?&avAYdx^_j;VPTi%N~w1&kR2@!YE(xCC50#7@zr?$2rUdN0{VmzF~@9zU4c< z=Le=az)AM`zASK_)tq1}7ud!rK4&K@e2>?&h8|bGokcd#N58kB=aqKx|MBXrAKica z_inLr8NSP?6~5nSMflF6u5boWcQ_4bWq&vqs3)8dv?`nxv^ty`v?iP%v^Jb1v@V<} z)EiD0S|8P`+7M10>I-KNZ49RnZ3^cRZ4M_BZ3$--Z4IXtZ42iXZ4W0H?FeTX?F^?H z?F#DmyMy{{PdM{vZ`2G?e^CE_AgEgR1$FKF!(D<7g!=^@463HVp!Rhr+)ZdC+<#YS zG^pzuiyC4Y4|g3p9PU3f5$;5EB;1o|GN}EV4yvY^plY2B_b!?XcQHB|)OU}CI~yGj z>U!scy51AvzDFm+69AnG>TfKBrvf@1RDEZ{lLDO$&kS@fsJhPwRriIU>b@9M-Is!@ z`*L{Rpey0YgRX{W5L%4tB>fbgOMmF+@PtCYgl84{H9WP@wV?L(Mo{;BDX2ZT8B|7Y zMLnlWG zHpbXKg9R8I&u1STXP?j5KHK*@eE*&8&-U?YhwrVP+10MJ6280VpXuqYuGC#sZ?3m$ zIG%$Kd5Lgbh8yR0aECb#m4AmKfg2GE?fGJrRB8*+P;DHIp>q3BB!ki@$QS!jA1VlW z(L@I7MtRY9y+~OYszLT*K`?M6h#08Uhbm*JUMf~_#tpij7`yVijlo zC3~_`RFQIfuH3y?&403Iz?0y4jd$VBWV9F9$mF!!Pv?b z3wwvF7gmQWFTb4PNC-Dy#=qqPu9K_6`wtRYEadXEKn6W%N2Htsc(5xd=N14!V65|f zs9Gs@Bb2L_(|>pfnpY-Y`WBM3#%fJM-!fG)shCrT(c3@%8`aE|$I2N~{SBTv{)eB_ zzUAwd_tAevXQKy@EH^GDr15LjG*uKK=*q@NEG1J(rz;u7YW(_V9pi0n;~nhv0JdK} zMs6cxT$IZ}KQD*31n4W~DF7aOsS|HmY##!^&W&N@iGR?DB1F|7DdG$4-6KH?d6A*o zC>~=?AQf@*lBMrix?~d-bN&4}d;rdkPmW)@j5d66*`??%Q<-`|F--*@P|U_-LDsWW z`1(2Ae$LHp#bVpd=k(_qW2`JXmh1nQg?q=w_AZ>VSP^uLoo~(sc1qxfB~_J%c_C#7 zIga)DtAAf5PXgR%(4HE!#!DmUM*tFl9XC%!DtZCn-cY?(uT@ILQjz4@xx(&;Sc=E; zpgXg}6T{hTeQ`Z|{S629EgT(PxbML4rvo<}IB#pYQYmje?|ah$bYm9s4^P&i5PDCT z`{KPU_uh-cV#klo$sPu{7K~hkf6S!-MwY_+ihp#@7XX;OS@T<}r;!*^mRcl0MGC|v z%;byg#e>LnKD3`ow&80g0(6N-Od%O0Qu$1KDi_35O~@;P8B@+z1S77X_gc21WB>ZA zS5Yi0v8->qv0dkks=R<|0Q>&Di$t&lDXLP;FV+k48 zTz_%4EGoJc%kspE-For4mJ@H`@(biq(#?6$#zDY@1<=kDeSM<;gZajXx`k4K7Tla& zuxWvoF?Y?L3x^jj9KLYRzfA|OIe+areegMF?U$wlx81NQaeZ>lA#}mopOT(!q?-=>X;qU$TGE*w~`|mmFStwEkAbllgL6F}#oFas#>C@gSG$&*jjkqv4ep zwd%I>UpjwVwU*EJ^KwKvJaFa0%h-M6QI>!15x(n3$kSvySK!LrGHzwar_zjK8h;2d z7T(bYZnF^3dkg{fEZ4_?RZ(FsmKOvYP$uMN6#%s&pp8JQ_|Y4>S9QZN^~L7(nlVv| z#_8H;){2R!Bu@RlSgRHBN_K9Hn(>tWOWiQwl!}{3IEFt1MHc19n|8wq`l)E znxx0IC!kQ36-D1F)}Czq#*CSW-WyG0$(Zy;$%sFA&Hu~L1Vj7oA-^E|0FEX(u9LqF z7Jo{`QB+|>O#~tZk-&H%u1Z$z$inCG8?V3N#>bnX8?L``>UV|qv1K1zHr8H1gg2bF zcGnb0SMHiz7ZxmMm1FX_WBZLa+;I5uTqcuy{BRh`W^?G7SUh37uAPX-^n@N!G@e&w zTZvImmL%DuF~ycuNmG11*`mFG`@8VZ0e?dRu7m4_@v0O{eo*&l0IjH2So3_=Fc1O? zemSrMk@~fIP)9Zm@TaT2yLWpFT}5fn-?(PMdC~aPvGU`Ozw7Cb^z^NpTvKj5ykP^n zwX)IQa$#bqGLrA^?AvhGiaquQcTJ^^^5w^W{WKCjlCd^8)ipzC7m_z@2w~_+vVWK? z1Xv&A)^g{;JLOrgg}e!Ru^8U5-u6QfXJErPRuyU0%-HAB~EH%3q{Du46q1&0y!z; zgG~y&5Ja~K2q~2kIW*;^Iy+Nn>$QgK0^}Kq;5 zzFp+iLb|g(<(SGLMIopw)#y!iUgX4FH|AUf75yO9HT55?82Vd9*OiMU#TcrzcNPUC z`j#Qnimpn#rHFI_#=cGRoDPyy52Fcg9~uMc6Hp&shLl1g0KzFP5gIUx#s(4E3Nlm* z(XxM{t1gnv`86IbqQ|F~Jmy2R@tf7b?sP;`v%ez#+IVq&vFRf|C_#xeeqBlHuAuEgC z2Q|5RW2fOb#;kVXuK`u?xHb-0X*;3QZ81>DBrCOg`$@#DosE}$qJK@~9`fxEhwts? z+RVSxbVHx|%k+CXMq?N~&z>K)U<&oW<=NJII$4XT?~#+~g#4N5_d@RrU%9b!`i)z> z@2BJul7=^q0uLGC76W|Q8Ds}A3<&VZ6B!4o)fu@J3U=9Tud$v70_L+M%EZD_d!Yqx zg56|`Xc>OtQI#4o^?%%pE;?6@8B|@lX;Z_xieeC>-EaTs+qX9U8C~=Yb@U%NrJ;%JNVMquKy`Wf zSw}|~>F)Z*t9rC!Uq>)4H$DIo!G7rd4(R;}fU95zI)Ya3)_-fw?xtctaH8f2Emxr zb|m2l$L2}0i+@aTK5)$C+$L@pj16UM5{Mb2aI^|yqeyKSj4?cl>Y<2UqV>o^7`uA4 z5{?vbAHU3+0a{*>VM!IFf{<2*$sienh|`Das0K8TqIdNMQGD5k4VU2wwCb`|2(3+z zSJM;a^Y-=a?W<2r>^y?qghL)VG+{Wd{uCR#iM~x6+kZ+Bn~uLvPB=)$Pswo?$!MT2 zK3PB<)CD#6Ok7r5wW{_i*SAWeZNu3G6E~E%m1BGR^7zQkECbN&p-0FB&|0WzBC(|7 zqFOVy`|R|GWhWuyXJt38a2zOLuO5Z5Jqnc31Tr4-ipowyGZpf#PKTPgCzRnH;n|vD1Q~%$(b?8IK{}@JSEaGA~<%8h& zb1r0H>Os7JU0_eGVma~Sb}%*4_(dj~3xZq}y+5#@WAWh${S7-e_J!fDF#Mt&G;#8~ z7urU0b`ItIoLeu^fVM-(BY!Vlc>LESGPM|AI`xi?U;XN`Lx*5w z^~N8_(9~x9=+xurS5v!oqjgVyb?Ko)Yj%ftag^uC&4BH_08<-6d6BVjFBDVqp~OS07ZH-8jrnkb6SXA(o>ky5Qzx<@Wgl;zK|h|ep7 zV}r_loy$8rm*clASPMis(xKb6Qp44IV(oR3ErySEcSYN4JbD>Y zQs_lHX7?=WNta4#2qoLvl7-Q;Mhj|b7;5uuH%RwHV&!&sL9TrisLk-Y;of{tI)4tF zf2gf6+MZ@A|EtG=PdyH}RDhdeD1$P!4T(&K$}lnyc+->_DkT67%<+m47ZhGr#l|jK z<|&nADyXEai!u*PUv6A3s^k!l;KK_BohljyQxpl`jG>xYFz^ql5sn2s`-}J$&gAmI zAIDIMy@5!P$Q~+$eF=n&Ny{k0)PEUrwJZpY$EfD@YK_Mv)5PDTQ~lXq6`gGkjl?1b zI@>Z6uD@6CWn9pxrg~F9*5l>gtncD{EMe)v)W0N0ZIwd+2j3&ll3|cJI21M&R99vT zgEkw8=@p>6GR>*h)MuH#!F~}uu~|ykPbPBMd!we1tdK6pK4uookHyMqA%BMz?TsGJ zC7!es(q?^Vi0Gd%eVpVwYwjIt&i-nm$JDn;(l*`fNqp7Lsdu>5Ek~1@Y3UH( zCC>ov_i)=7ZnF1czt*dP0MvRn0JY}J6*al{Lf*^xKh=-dpz#`;i8CeO| zBFwq+dO7Dk=4!^RrbaTvHGe35#OZJzq115UUNdhsH1{zt2l?F8ps}6^U+5Sl>05b} z5#!}_EO@^lyg!Jg%W*M-_*>Itu;U8?S#J#DU)vvaq9jh*jH5=$d(4RNn-ygixjt~Aj;ifqJ^3()=s+Ira`1eq+3i(M6>Q00ga_+|) zA9?gqbZ*xRFElp2@P7h2()dTziMpmPLRT~Gym23sL0*0J)gvLTy_7q`y^VV>kal3S z6*g2bW`${L3z{Aq6bQ5E5Y?*1kODI+F8lfblEqR;e@ji;1K+@7h*}N>v8H(?V51E@ zTntr6RDfCwP0Gf!X~zA8);&`Inj!%xpsl!&J6@|gsvs$nB7a~ibPdE(Wy?@BQ8`Pc z8qq*QESP|TI2zkIzTv=zI|q`ULWz?}2vEpy+b>#l)(u@H*F@<^myEKpZdFpL5YITp ztecG>WSfrQ;jNEY+TBkwhW9;Nz5A_JG}?HU@mTx48#U|gEO5(4?c!rBz)nY1$Cqu- zjYR|`QJTx7%6}sZM4?ThdcjhnO3qO<3D-s^b|u@_t?8*G64;7U%fe1HnoEu>>g(

        MUit(jBs4rAw7chp!O}NuVC2bM1ZYZHk6|fz-PhgI3=d%|(9++sxtZI-?cuKBZsy*=-NC()yMLE^C-*4#aqcnhtK9S4H@JV~ zUgmzy{Rt5?gm$C1qYt3Zqi4`}(2M9N=-<$PpjU7jkK*0x?kc4BqbjpI38Cen1x*2M8WQTwyAmygbi8SJtGk7{X2w2T z8N)+q?0*@UGU?|aWd@yHzG7~HVoMxT1p$wig*q}qFvCpW0UuEhK#C0>fNl1LgLR*So0{$Bbr+L^+&%7n@ zn|}mR!#pozA`1yGniLH|(ofGfxarTzLBM|NMcdn7!)*u8Evmm6KTrFrRIaN&Xl7hcUy3zpxzV)X;}PEJmA zZ~bzA?nth0?UDR|*poey?G@3$XP0&icYnaKG~L;`u&XPTC>CqQV&dT?$j!8890yMB z(iEh1cBbP+Ai~AOB2iK$QMFZ%sv`EWsNTbaydz<3V^MOWrD!T}f=D_d3eX}^wsX2^ zMqJI*92MDm?9NntCzbwBn{7OIv!umMInx<_V-{dICqCl&n+)$$zPy zi~=o(>Z~kAvXzb@>Lg4=70)LQF9sktVIx{W=|;OisepdO&W(0amPPaER5V4j{CHefL28WOvlwBbKyRzIq9Z$H3z`nyzGPN5Xk8qiqiG3Ew^U0MsX$}b z-;fZADJS4b-B<1{nFxkJL`Z|j-$*6L@D;;S46OTaAgYnDsc0EdVnt02dupbQcv1It zskh0YqtWFp^vu!3{G!2bo5s7s;ki1=z?RpzP{YC#%~Am z$Bt!tda}ok^M*&G2cZo83lfXCb3IXgoUigWgC?*ocbduRUlRH4C zf@Af3kxazIhyw7)vMw1m0+q~lU>GgSr8?9np_L9bHG>KYCP<@_P6fxdEmg5S$6y+g zVVRaH2ns-!Kq%`j06$SxWvVEI7jEra7lPFV>-vAbe-Wmdq}!cUzzLb0qw#{M%R0y| zih>Q((x|FKF`~`Q$Ax;p_sNT7D|bHAe?rm8V=M#MT+1}i*aVQ>GUFs^4AeD@tyUla z3@0?@1E*m;2Xat0Cmz-UVPw>qeN0VchTuwIQ@kIwN zyJ^p7Z%GqVT_X{q%DSS;-BAZ%B<4orNNwcJ|inHfR;0YHL?QJ%TW8 zq{I|t#xjAMf%L&x8gmG!)S>C2*idA5r^#8uZ)+SXP8Q)nzl4)xUu*XysY|cCl6nR^ z8y-8mrgX&>($}wzxAkQ2kQyJqLNe#eLnfe(sfS9e!V(sOUhAOkG`>uyGhK0_z!iT` z_;Uy2^bMKbT;o@>Ynqj*r3ZWsphe;0tRL9?0Av4wFl**2H=r!L{ZyZYC4Z&%@kekS9;u&un6&#JaE=GUnDSSimM= zo4HPOHUT9O_;om;7Mi*k??=^X;}(Aqhd*T%Cqh+O+D1C4GYm8sLO5qqoMrld`i#%xndYf<6k9Bz{0_6 z0f#jSB^2Y$*RoFU$u(BdYY##a#3otJpR+PQ3(NdM(g@EBc>6p7}I%(hQX zmnuRnlkNCC-Nr-6QeNSUwf6SJZ+kw*Ge#IkOHPx(P)Z8N_LdO#v)mB3gxkpNp7 z8u&p`sKBGs0V9mRAA>;nW5vN(p{SrZxs$T*+&3rxyHCpAniG08q<1b?0O*@!@K>J; zdLhwcmIyY1z-*>$RxW>_VfVJ?QA=k`on$TB+w9yBUC?j;lPUb{^ubN0xrsWO=KSqE zardHMlqOiQMk4GWbZcNgWJgh8y~_^7HAaWNh9XUQQr3}r+7t$V;vD!}!tLko=05e; zz}_^x3^7|cGvZ(aMS_-7LLVqTp`#qj5U}X6(6$pA=F*sHvOs^o-7w-z^%PmuYt(W^ z_$Km29=kF-HPY&8j&rNLbXtLz20e>jo;3sfmo_orZ_9nV}+p) zTcOic^PAVIV%mS7Z<^kAgnnilPeXH?gU~#SEf^tec=}*u-1$@S7>2RMr_kKtB~l_G zOV00{4~Q)j>1(2c2ns z$neAJ+^)u-=1DZ5u2`yBBWHy2NE0WDTn+kR3%3tw@=<^8&D_J>r@5!N?{P1m7Dt2@ zs&_YiG9u*`322&)fFHe9`pNgrNlP|i$Ly0nn!&;L|DT^UA7(XfTftQB6`~}4hMh!q z`U(lr>ZSm||$Jm0{XXF5 z#df-Xf1B*+j3*4cxOj4Z>Sh~>KT>9#@A_aV0(WC-pubxW>$@+Vn8D-p5K-*QoJBVO~tquN0bN} z!6=nTeG#NtTqqHoikg^Wf$&kdAf5;dFew!<&>Ix}62kFRqVW=zB1$ixG7{T(-U8Ya zOO39|B)fcg+syWXA~h5r3Zfm;Qvha~GYyedW*s$Y8p_>{sQWUJ8+#DU#!; zOiMI?rZI_}sW_BP(AR@Ho84t|^9$J&J}ni3=9KGvtv>q>Dasmd+^^HyuDGz49HROI zhCv^s3Hr8OyZe(1sE%}cCpCZQog(lLx**xVd)M0_^+9SF59o9#S-Z-mC zpMObacKPWpdhK2 zsDU^r$n3gFib%sE9hM_>5tXtPAy6+R;&K#err$(J(A;G^BJ*n}>#-BhyA`*&;7DaK1i1WwZ8+J4^lf=U- z`XMFaH=h@Y<%&NLgcn4Q>Ziny|rmwV;*rswWb9Q8YLFTa0C?N z*GQiiJ`TnUq2+(|AKs!z=t>GlM1PAGh^s_6f{oc8)HT|Ry1UMtd`8T?T-RVHAKqg@z&h@2r2Xrm2%^)>P&8RrsQf$QeVFy52gDsC;enLD4`%U#O-Kim!6 z5ulWJhu&>Wfhl#NfE`>296hMC>0+E^)svtd;s%$DM5KBIb z9bJ?8*jqVR>f1c9%EMnKefTe5#{O9YrM{oOd*!(IP_p9d z>d0l2BZ`0FRT2+jcYM=BE5_Xi6BQ5QFIhaI=zb;nZtRWU1cgh3tNgRN$I=5AH;xUa z#=6&>)5UJk*g$H$>zr+)@7!?SBR4KZc*)_1w~E5K@4Ip7(s5Zq{)L;p?BMliL(bc@ zbK<(kcW#2)gA?yU`r1prC=K@|bK2uyL|QJ{yWoGH-nD(L_S}1szW%bu<>B6BPW{5; zNM$K6y?4iY{o%>$hH~y^^yav|tDZ{LciD+GQ#T~6o$O|(l~^<5qrV1U2Dn<{7J|0Q zxy;)kG`5vO2T(DMYQo&xI?F=mi)-?qaCYWrwFf`?U`@0&EH3)!mp{5lM7oXsBHB6< zdJcczdty+ zcNVt=B%RAc&dm(spraH3Ep33Ya&_9x6Lf#MYNb7A=c=dq3LtE!adVba>_&CJJT!&@ zwhKXJ+H0+~xV5Qy2ce3{d_|fQr=hYQ0MLVY@1+T&iuodW5=ZUHTi!6b^U25G^WA+D zXvCB>2?W{3ThO}3r;*(F^L)QXq06L-Nr@ndillc3wxUo0NVd5~B)Y5`u}r$0i8_Bt z$4)sH1z7hrK`|XkRUN~RpeRy2kw|$;>l$b_z8Ls3J)ZAI zK6CD~>$SiJ5wsqZgHoYH`;nisNz=E8xwp5Medg88oRaIziMv*14m{J#wwV)8Asjp= zWECLEXL-YADYe!J6{h{3tGHuH<>LF#_7j+SqhRQB$x(JZvF@y^e*T@$yz+mQ7=X(K z`Bmqnw)91op}USove}+&_V};TM2NVNXc7^@lg0)TvSvd`#dp(b%h9N!TaQdCZB9_I zUDt>tGD4a-nimAF>{4>{QiK!!HNSfC63deJ>{UaYs~4cPie=IeipgOS^hPS{b_ z<Un2Z>Dht}Y@_i${1b zrUJ%$$p;28HdjJ4GWRF0f3e5yBQg4!`N-da*?O4g_aBenaPkM|dRuiytk1&nnm{ClY83fIvSdqJHAjk%R)pXX)w?X4O#9TD0-o0(9} zJsVURzb7IJR;b^9vxT2MfS+UBQf@W$F!O4D$LxPzo`sz=RPVHMKuGPHG*8Hjpdo^S zc+!Lm376lcF=Z-GbElCiTjJC-GlRMW15^+98)3K|Za@+)Mw~?xF$FL*TWHp80E4AG zABq7zQCpEE0h(!S3`Xo=1TB)OZb?YTJdZ)(;@{*uyd)qJG0g~1RKfAlx^E^2lF5PL z{$zi$e=(6nVmg35qC{NJBh}VOeElS1`CQ}Im}ioJE{iTAygQg^{1lMj>}3A}_Ha1KM3jlu+1qIm7<6~A zItaMH=H~v6{B6jenzZyB(C{~L=YqCzA$NZhcME75cg-2I6GB7bzeG`ih1J>M75q|_ z=IVh7)Q^_xCF(Orb7C`9ga&o?YpzUTY48I?V>yG+8=2zBg4>U@*cS#YVT1Mj*zRMG z_A#0mfnk4Ldy^&_mZ>#q;uU>^u3<#t=!;5LS*B#=FUVQ?bvY~lGKC!~WQF_TM^%43 zhHc7c`TKbft!W&C^rI`&9gv+=x3ht=_dNU9wrL6n1C_$hG_E_mY10V;r-CRjP2l#& z{IzTSSYKCHR?c1rze-jP6deNSaN|e&sEBOY>IT}3P5RRGo%kHSxbd}|l8vt3`}(Ys zZQ9BhuVdrW%~iQ&Ajxe9dHV{$riXt|!|7&?%e+HTKNF4>!XXkuA4TZR5~w7D1`2BR zfLY6H#Aa&)LdW=^xrRWbt%V{@lxsTF1G~%FI9PDUG+F`ok{CS$v>IRoWPmi?8X#F; zHCx`+dYHT|@w}{>8`lmG44`i!tOSCii~BGlgkr)YOYx)WwU(Am0!y$(QR06AHK7dQ zCDlqe8OlqRVtTgKJMI{09UDQ$qMrN0ESd)c_>IOjHv)AtfHU#^2io9~3ihX^aFHhm zHa4Yja^OZk?N@s-k_2oJ{I}G>LQWOU`=ZHYoS@x}`+2=kj7A4Qp2VV&^=z5fEHmO^ z;5$-8B*aimLR{@deEA&tWg&ma=Bq%1x)}J+5$=BOUEKTTjOx7Db|yX&OpA@-IJSF! z=siLQ)2PJ!N7)t;OrHYkSs2XI!O-y|Tph$_l|;hGB}B4>K1di0o%R`xEJ4{mL?KGV z(ODwoG{KzM{mi2m4bg<@By`izEI&p942=eQM-8zj2*RO!#j>%Vau;(kp|lmw)h z80P|giD~kYTuzY)$^}F}3#~gO#cge{-iC7tDdlSATA}Tb8|V4>YE3t$MULlAqMXk+ z-tP|rOUkrmBw$GZ3|@CcU1-yYyQ88xwNp=pz74{hP-|Mkb|FTOtS& zZKY%^zCDaVuLBw^!?85yh6@l@YrO0b0&I;qhAj#aL9`7A?kqwanU4vgD8#}nl0+rU zV~UW(W0kQ_74v@*LKGq)iC`52la`Ud78%Gq)-WM(;$znB7 z%i2kuMspb_yWG$CaEzM~%Ziw!4Dfmywa8EUIe%hNN5`Ux?(XK@X6At}%Xb;jI?qGV z#=nP;<4v9ix)NnB^-*57d5B<5wh+7$Pc74iTWHwK>xzG^7Bw4<@$5!}x;;;$G28+z zjPZaMvFTprZPRM}J-)PYZ!ffQqW3m8lZ%R463FE9W(4`^NCa4)31UwZAHD~)tv(}9 zT*&PO>UITpHTNd&Z9w7v<}~!}zowtf5W(psjdO@#`+r-!h|eQl05E>Eug&xFek97o z3o19cm#%+@-0R37O&CD+kYOseAMil-WFUKnq>JcUkRq1J(-KEcIU#Z6`1vG`n^&be z;6mylyzKt^V+>xVRVrXu&w|7;Tcw&Uaf|@zQ>{!}TB0H8^Sc=7Lm+Wn&%}>hD1Ine zbYtU(`>BYn8S&$vrtd)fxTNtl$Q)h0@AYs2`4E4G^wn7NDhqhv5)t+M`r2MfDnqdIKWy&6%@mzs_N(cSSlIqM0Wy8u9%IU>1l+_@>GM~1!T?f_X~cAo;hV2JuGa@vkNE9}ckDRa4ajs2;9j+*1LRIc!j~vL9TF-b4mNdw3eePM zf3E}?V;kTg>+4)g{!St6`jOdvEijvVXg-|#WBVHU<+;E1lcbL_bpC7vtd~88HRoy3U_~5&+Xx^b^r<92fQaPOZj z%g}jb`*3jC_%f6Vw{(Qt5)4X{&Ho*O>jL7-fMr4rb&ul~vEsG(u zn`K!9340YNdZ7|HH{gJadtF<)}F7}Ca>5MGZ-_&@n%w+F&Lj@ z_hRuFZ79EJ8^rxLz{e!?Cc9qY$k!^vne*G>nR zJ(yQ-i@HAStM^r>OKBZY!)V6&| z6NzdVlA*w0@`iuwsP4GT#zT`;N8(N2)BJG?I<}(3L|Im4c_1`(!jNAy4|=}`Ym6|9 zJTF*LSMqJd$g)BuAj=@YxQDlF*}9bh0#o3d^F{v&>WM6hdu@1~sJ%)8!JCL0Rm``k zu4^#UkKul*2_xUuJm2IcM>YJ9D5e6(I_M5Kwl{T^zXN~1%IJL?SK|7B>esjtZk$`f z?cy%t_H&oMmWAyUlx73_-fmzCbTW-@>ET97S0_$+}EU7}cCDu_!YVi(>a! z;`4xBUz>kTH90O9>VZ#?KA@lmm*(m=rbV%AP`trO+)0v0{H=ZR{b%605n~oPwa`@PV|>e3f4# zGyWzoQ{IUN5#_lqA6+;&jUg{kp{$;v`CwyR)eU+0~)H=FU)C+^xUF$ z-}`^~Og2%Sn~f@0i-w9i`TQvUV9J{kSu5}>UTPscf_>4_j#j8MdQ0Ok1y|vhJ|YK( z6D6YQX4~5zdF14{{mmNjC!pkp6NSGki6rV6f&9o)UU3Uqx4RRmhRQE{1S;7xIx3zf zJ>>P%e1PqHTmvXw!|e-g7q90Ycr6alngxFzqOI*$@*$)YstjVLC-SGErv*vjmhE(q zc{~P%Vzm^)LFhlh_Ol7M=bPT%rOy1Z0;r_Y+{%Y}$Fr?&!a7b%@b_r3o_MUcPqs6* zomsIiXJ)sXWfP8zRt#4vBdgBsHXT{gK}QuV9wSMPn`T^5WOWN#_j$oo6-!O?Hf58g zKM#NJjNW9jHyJ2-kRClv-@IAx>e}7Yvs+JT8GW}2G6W`uB@4W)Nwi~gR`O4^IL3OLmGaHY2lfVVG{`PXa4}7Y&suH z+9tUj4X(&9YR9)V?!vsQm=Y-9hU6;bQ**5lRMA$p`!tNP%w~+fC%-*UMw)}L=Y$%H zn_6hc)cxx_%N>gpj2$bJ$`>ZH3Q)5}dG=DkyxUEU?c&k4<&=5({int-myOU5_`83= zD;I>kasaf@m7wk4Fg-VT25*a&=gJ)2vKi;v?3|na+H9K)6F6g}JQi-1$hIJZ`*3Z> zSh!(b-3WIrs#j~_9(Hxwyl2iX?EX~#`StNVU;pr`!j61 zkka^5_*;9If`uNzBTxu2)dG5r7hHc8izG>q1D9?BahUY*Bp)S;kQ6Y95|vN!IOL}- zz2$`SLq5fX*#o|RU~*AM$0%r^ER1$HzOS8E94v0fm%d_J0yc31A*xbft61OzzFWYu z0Ts9yI5@@$w&nukBq8NCc#;cGZeG=<>~a)tj)p#XO@4Y7cLC6iQ!&0d>36he zVuGNKM|8j|iKwC~8nzYlI%N@Q;&a%Pt&MC$8wnI-1-m}N`X*Th-~!=?9Nmd<(!I6u zf4+CX^ng@K813>(ZDg{&bmf$|b70}XqW7pPz^>iR0Nt6wNbRk~jzwB7{T7;qw zq5?7iOc5gV#QL*K$t357oU8*Nfi12(c^6w6&f^4BV3KG(81hIpAp2e*lr}cUN~IXy z5-SOfOVgK7dP#a4T6TYFdfT@2rKIa#yzfPVit#BfUM$A(W~?{POO9@1mGOn%q6JUTfUYg`efBLk#; zaD+CJ6Y#kJf4w`M?w%UyV*BW8<44g=&3a#b_1ob-h!N1p*Rg*+jQiORbpcyC!z69i zBKVQ;Pd&obV{W}=C1Vbylr(2S!_5*=={=_z(y0dF+To|)vK;6>Vm?H zN;!pon99hgSUyL*ES7bDd*j*oK7C)T7-ym7cvq=-_j%{<-S*L?n5$&73wwIq_GL6i zD~(d;9b3-bz4!d{?k<)Zx5xCn?Ydh=J;k=Osibg784AL^hW?fuBa30Ix%N5#dC*)S zK#mP;9Gv>s!HolO#YwncJu3{?=6*$AB~OzjoX2tQNqh5#mWHu^(M9_%{^P~_7LCpX z(9uJ4?pc7F{%?;%_?6Z!%A5>f1~3-xAKKhp29*em5^3#M&SJ=+#vevvjR)f;w%~PJ zY;q!&jibxsKX|$H@`I6)k;n?>QWJj}{SN6P@y6TYvG~M9{5)n0VEJ4Wf4TIlsfXEA z+j5p&e>t?}9VLH%A=h)PeFDf03eZfC+rk|UJzD+-H2QCGKj3}?z1EKg{0thZ0t8j6 z?IESC&A<)Y$9Q^eU{EZ~jH2yr8{QA6+t18|o-_;YiSfyo}w8x^lJHl@m)kO80Evxe92FleN-H#xo5> zmJSbfAKZn1kxC?*sgkIkaj+eIq!x=ccEJffoa_32cfJ-w3vOPva@pdAv71q^t)#82 zL!_vU6Dxg-N?jKqgEw?6sR-{nxMZ|mY7e|f9bMlOM35i+rh903DM7qSP1F+r9quZ2 z4An+{Ez1bqiO?07X_|;hDwFKM>>YPpuz$yv4R`c^kj32ztIafUd%d%^a>?!e1Df8J z5ye9O;*t7LwTN?3v2Mk?CYN1x%^mwM*l|y(n8DW(wCUPF|Lsdw)^M>pRId+Kf79*3W87*-GJ#9g!TQL>z#+v<6E}ZJj^gjI z{koCORE(;fYpt6OHy%psi>Zx)5E0 zE=AX%YZ(bfxsXs~8F*O?ys`{Z(d@mxXYT<{=G*sMh}35Mg_@g+7H0Mv4fmo9B5XIy zFcqp|X@Xd+*5-I0<-`1As7c$ys5-+V%3h&aJ<2!tT+4;XREmT?4Ef@Thkz8CZ#{Q^ zA!a``I!{b6~vrrPmlTHFxSW~ z9>4r`h=1MX`~|#PzpK_wr5xbQQy{ULL zx6Vg@-cnhdYhQKK*|wB_yS4f(+qUFcwvpvLp~hpwrB_SCaO$YPPtfhQ&XtQUU+=9w zde(WXrGfrcXK%iF!2tf);;WW(>xsv_XC3i z20Ps0*=_xq&&=O{IJFdy<~@naNJ*(*^3(p86cYh>tA7wErWC0%PyV28?MhF5GTr5; z{Nmyg-cVXxgdlp_P!;V7^hd+@jfVDwiWD`a{+{huOELckwej*OU_pA+^nG&_ia=2o z4&nYcWQs&MlgorVLakuyV1?J({P~oPotZ;>t@&?|><^89nj1Nbkgr7g!r6sBTx%`> z3fzc+$tRD0h;1`Vw!!)MZB$Vx*~ZROAj{TP*J?%0vI5)woN7@~`fYq^@Fp3#3Hd(J z7k?*__sb^#X+9$TOtfvC=fz)`rH-8ZDIUp&-=D?zu&Sr7$@b&lLXFq3dhtC}`6H`% zb?tgqabPunCN$R*pYZtLZuT!O${R%_e37n@CA3-L>F?RUD%rO9zYR@_x{Xgrwjs6l z&pJk)C*#}&+!5|hkWn6I|6Wj={{IBl(iEr{A#0`vhVEc|zr;4pYRVH#xMpw}Y3-&E z3B=~!mKZ9ignNmk(X^ZZTCfnxA|a=TeBuA!RQgeW=u*TsT%b&8VGC?S<5Sbuh-Ef# z2y@L%!&zPR(l}&i%O2S@P@W7}NxDW7wNAZczgsu=E7`5}6^=WGy{va-u~RW1^CHOG?MSX`;M~&EM9<)g%Nw_t#Pqgz zIEsva+Eg<>SoE~q!gQZSw6&VQ=<{}W6k`|lj=h=sYC5|pnV}iQNR&%15PdbZAQh)P z#j+b8EJ#;*pL8hW8cxHandt z6cpsO4T2}B`6EVU}5AmSFw3DHy%d>k~UAybbf z2ixeg?-l;WjxIYl{|+>5`(Xb?!~Vx7#-*6dYqNj2X`)s)3N40g;+!EID)?o)V3&D+ z?l_cS|2p*hYvq`H=#QF<{E9q}}c6Q}58->m)4QU~Bja-ECwi&>LT!UhjK9 zelh(2hYoJl^gjmKIZ|y=9JWZ3Sv+U{PcB@Gl0xm(0%g0*w0vU=%^hx-XB<ttf; zD1PDii=^v?SRn>S_tE^qWeW=hIOQ9EFMc<-UUPI`5uA9`*u1DuRkOowLVPHz`l^MG zHh^4ip4x(rb~&+_^D%bXyt%M2pI=z`So6}gdGlA+X+|{W{@<~3iSg=turtY`YV_ee?gDvHwFsGG4X-twh*3#ZD>}vVjx1y;sem8EGU{pjg5X|^am)3-8AlMRp zer=Gc{Hysj<@nlwW&!|X53qy)0C=2ZU}Rum0OGur)z{+rZN4&avoL_bgNNm3Vf6n$ z|La+@nHzyz4hANWC;*5|4w;h)Q5}B*0J7`{nRuLIVPIfjp&9=Fhk*Ql+S!eSC1_|i z6UdKv{EoKC3I_lH00000006oH906_t)&ed9paT8_R0G5WEChB0@C9B4$_5?= ziU#Zlcn9bR76_IJ9tm6tnhE9#ateS7lnSg0%nPy)o)9V!un^u6ZV~DdToZpN6Py&H z6($x&7X%lc7#tZY8JZd{8w4B193~vR9h@Ex9^fCOAWk7}A|4|2Bg7=GC6p%|C@*004NLV_+L(U|>jL+Qp#H00K-v%msuD4FAD=1^_8-0%o&AQbqxP zbJIo?_Lr3c!bB;#UA$Y1W$2*pYaU8;5lC4B|7*Ozu^^L<9GalL%hKe{={GS8~@;6 zbnzcJJopG8YRe(dCb8yOPi9f6G>cW~jCdC1JhM`n@_M2tv0Nx(Inpi}deYv1+|H}b z%JO4g)=B57rAYIDFapUlGC5Dr%#&k6DPoaD$Fh}j*%u-4?wj4C<(y)rI#O9wkw?@f zt0_bAyEET~Ga@VD9$%cvXP(Qm$N!-1fUB5{-6GaKO_EBk^a*`h7~RC0^csoO3D1t@ zd?HQ3g5C>-;kc>@cy$`FFi2;Aqz!YKuX)OBte2G=$|P}3I#YA$k*m?IoT>=>RMvji z%%So)HK4h3Kpo7e2G;4gb|lMKd-c7_7m2km6JZ^yK|re4^;8$qcYM>&O4~FJ0$n<( zVM9ZGYknL51b%M|;t8#-Kc3wHtj`g*F#oeu4XRNfb zY~^SD8i7`0O~bSf5B3jOpnTTLBP%Y`q#=Ekb)R=!mQw*ilCU)2W_b{@h++wfmF5x4 zi-4v5A*+(Y8l*#yjrfoiqmc2Tv?em&uv8ffmggm-v{+|%e=XoN+>`iSYNv306$r=d zXvCFc25RaX>)Dvxf$&RY+m{1B5zXlkbLsP@$<8fBQPv4+A{nhibF8yakMyy$dwM!u zgJBq({3cwsWqgHi%BbfR-D> diff --git a/svr/static/layui/font-ext/iconfont.woff2 b/svr/static/layui/font-ext/iconfont.woff2 index 26bebbe33930920f3eee6ea27bb53c1d887d9a4b..482718bfc5ada2bc92f1e1b0bd47ee74c9ff6460 100644 GIT binary patch literal 18840 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 literal 18196 zcmV(*K;FN1Pew8T0RR9107n!63jhEB0FDFz07k(80RR9100000000000000000000 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