From c3dbe52d5c009546e5eb2e44172c1d5762ee5d35 Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Fri, 3 Mar 2023 08:09:35 +0800 Subject: [PATCH] =?UTF-8?q?[+]=20=E4=BF=A1=E4=BB=A4=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Deploy.md | 4 +- taoyao-client-media/src/Config.js | 2 + taoyao-client-media/src/Server.js | 14 +- .../src/{Signal.js => Taoyao.js} | 207 +++++++++--------- taoyao-client-web/src/App.vue | 6 +- taoyao-client-web/src/components/Config.js | 32 +-- taoyao-client-web/src/components/Taoyao.js | 174 +++++++-------- taoyao-signal-server/README.md | 4 + taoyao-signal-server/pom.xml | 2 +- .../taoyao/boot/annotation/Description.java | 7 +- .../acgist/taoyao/boot/config/Constant.java | 4 + .../taoyao/boot/config/WebrtcProperties.java | 2 + .../interceptor/SecurityInterceptor.java | 4 +- .../src/main/resources/application.yml | 9 +- .../taoyao/signal/client/ClientStatus.java | 11 +- .../signal/controller/ProtocolController.java | 42 ++-- .../signal/event/ClientConfigEvent.java | 23 ++ .../signal/event/ClientOfflineEvent.java | 23 ++ .../signal/event/ClientOnlineEvent.java | 23 ++ .../taoyao/signal/event/RoomLeaveEvent.java | 30 +++ .../signal/party/media/ClientWrapper.java | 6 +- .../taoyao/signal/party/media/Room.java | 11 +- .../signal/protocol/ProtocolManager.java | 4 +- .../protocol/client/ClientAlarmProtocol.java | 3 + .../client/ClientBroadcastProtocol.java | 6 +- .../protocol/client/ClientCloseProtocol.java | 34 +-- .../protocol/client/ClientConfigProtocol.java | 16 +- .../client/ClientHeartbeatProtocol.java | 3 +- .../protocol/client/ClientListProtocol.java | 2 + .../client/ClientOfflineProtocol.java | 18 +- .../protocol/client/ClientOnlineProtocol.java | 21 +- .../client/ClientRegisterProtocol.java | 44 ++-- .../control/ControlRecordProtocol.java | 4 +- .../protocol/room/RoomBroadcastProtocol.java | 40 ++++ .../protocol/room/RoomCloseProtocol.java | 1 + .../protocol/room/RoomLeaveProtocol.java | 58 ++++- 36 files changed, 588 insertions(+), 306 deletions(-) rename taoyao-client-media/src/{Signal.js => Taoyao.js} (84%) create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientConfigEvent.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOfflineEvent.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOnlineEvent.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomLeaveEvent.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomBroadcastProtocol.java diff --git a/docs/Deploy.md b/docs/Deploy.md index 56b0ee1..83ff9ec 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -368,14 +368,14 @@ pm2 start | stop | restart taoyao-client-web npm run build # Nginx配置 -vim /etc/nginx/taoyao-client-web.cnf +vim /etc/nginx/conf.d/taoyao.cnf --- server { listen 8443 http2; server_name localhost; - access_log /var/log/nginx/taoyao-client-web.access.log main buffer=32k flush=10s; + access_log /var/log/nginx/taoyao.access.log main buffer=32k flush=10s; location / { root /data/taoyao/taoyao-client-web/dist; diff --git a/taoyao-client-media/src/Config.js b/taoyao-client-media/src/Config.js index 94d81b6..3f067d5 100644 --- a/taoyao-client-media/src/Config.js +++ b/taoyao-client-media/src/Config.js @@ -18,6 +18,8 @@ module.exports = { version: "1.0.0", // 终端标识 clientId: "taoyao-client-media", + // 终端名称 + name: "桃夭媒体服务", // 地址 host: "127.0.0.1", // 端口 diff --git a/taoyao-client-media/src/Server.js b/taoyao-client-media/src/Server.js index a5cc552..0e3e824 100644 --- a/taoyao-client-media/src/Server.js +++ b/taoyao-client-media/src/Server.js @@ -2,7 +2,7 @@ const config = require("./Config"); const mediasoup = require("mediasoup"); -const { Signal, signalChannel } = require("./Signal"); +const { Taoyao, signalChannel } = require("./Taoyao"); // 线程名称 process.title = config.name; @@ -11,8 +11,8 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; // Mediasoup Worker列表 const mediasoupWorkers = []; -// 信令服务 -const signal = new Signal(mediasoupWorkers); +// 桃夭 +const taoyao = new Taoyao(mediasoupWorkers); /** * 创建Mediasoup Worker列表 @@ -56,12 +56,8 @@ async function buildMediasoupWorkers() { * 连接信令服务 */ async function connectSignalServer() { - await signalChannel.connect( - `wss://${config.signal.host}:${config.signal.port}/websocket.signal`, - function (message) { - signal.on(message); - } - ); + signalChannel.taoyao = taoyao; + await signalChannel.connect(`wss://${config.signal.host}:${config.signal.port}/websocket.signal`); } /** diff --git a/taoyao-client-media/src/Signal.js b/taoyao-client-media/src/Taoyao.js similarity index 84% rename from taoyao-client-media/src/Signal.js rename to taoyao-client-media/src/Taoyao.js index de0afb7..8ae3c69 100644 --- a/taoyao-client-media/src/Signal.js +++ b/taoyao-client-media/src/Taoyao.js @@ -20,19 +20,19 @@ const protocol = { this.index = 0; } const date = new Date(); - return 100000000000000 * date.getDate() + - 1000000000000 * date.getHours() + - 10000000000 * date.getMinutes() + - 100000000 * date.getSeconds() + - 1000 * this.clientIndex + - this.index; + return ( + 100000000000000 * date.getDate() + + 1000000000000 * date.getHours() + + 10000000000 * date.getMinutes() + + 100000000 * date.getSeconds() + + 1000 * this.clientIndex + + this.index + ); }, /** - * 生成信令消息 - * * @param {*} signal 信令标识 - * @param {*} body 信令消息 - * @param {*} id ID + * @param {*} body 消息主体 + * @param {*} id 消息ID * * @returns 信令消息 */ @@ -53,12 +53,12 @@ const protocol = { * 信令通道 */ const signalChannel = { + // 桃夭 + taoyao: null, // 通道 channel: null, // 地址 address: null, - // 回调 - callback: null, // 心跳时间 heartbeatTime: 30 * 1000, // 心跳定时器 @@ -70,7 +70,7 @@ const signalChannel = { // 防止重复重连 lockReconnect: false, // 当前重连时间 - connectionTimeout: 5 * 1000, + reconnectionTimeout: 5 * 1000, // 最小重连时间 minReconnectionDelay: 5 * 1000, // 最大重连时间 @@ -79,92 +79,87 @@ const signalChannel = { * 心跳 */ heartbeat() { - const self = this; - if (self.heartbeatTimer) { - clearTimeout(self.heartbeatTimer); + const me = this; + if (me.heartbeatTimer) { + clearTimeout(me.heartbeatTimer); } - self.heartbeatTimer = setTimeout(async function () { - if (self.channel && self.channel.readyState === WebSocket.OPEN) { - // TODO:信号强度、电池信息 - self.push( + me.heartbeatTimer = setTimeout(async function () { + if (me.channel && me.channel.readyState === WebSocket.OPEN) { + me.push( + // TODO:电池信息 protocol.buildMessage("client::heartbeat", { - signal: 100, battery: 100, charging: true, }) ); - self.heartbeat(); + me.heartbeat(); } else { - console.warn("发送心跳失败:", self.address); + console.warn("心跳失败:", me.address); } - }, self.heartbeatTime); + }, me.heartbeatTime); }, /** * 连接 * * @param {*} address 地址 - * @param {*} callback 回调 * @param {*} reconnection 是否重连 * * @returns Promise */ - async connect(address, callback, reconnection = true) { - const self = this; - if (self.channel && self.channel.readyState === WebSocket.OPEN) { + async connect(address, reconnection = true) { + const me = this; + if (me.channel && me.channel.readyState === WebSocket.OPEN) { return new Promise((resolve, reject) => { - resolve(self.channel); + resolve(me.channel); }); } - self.address = address; - self.callback = callback; - self.reconnection = reconnection; + me.address = address; + me.reconnection = reconnection; return new Promise((resolve, reject) => { - console.debug("连接信令通道:", self.address); - self.channel = new WebSocket(self.address, { handshakeTimeout: 5000 }); - self.channel.on("open", async function () { - console.info("打开信令通道:", self.address); - // 注册终端 - // TODO:信号强度、电池信息 - self.push( + console.debug("连接信令通道:", me.address); + me.channel = new WebSocket(me.address, { handshakeTimeout: 5000 }); + me.channel.on("open", async function () { + console.info("打开信令通道:", me.address); + // TODO:电池信息 + me.push( protocol.buildMessage("client::register", { - name: "桃夭媒体服务", clientId: config.signal.clientId, + name: config.signal.name, clientType: "MEDIA", - signal: 100, battery: 100, charging: true, username: config.signal.username, password: config.signal.password, }) ); - // 重置时间 - self.connectionTimeout = self.minReconnectionDelay; - // 开始心跳 - self.heartbeat(); - // 成功回调 - resolve(self.channel); + me.reconnectionTimeout = me.minReconnectionDelay; + me.taoyao.connect = true; + me.heartbeat(); + resolve(me.channel); }); - self.channel.on("close", async function () { - console.warn("信令通道关闭:", self.address); - if (self.reconnection) { - self.reconnect(); + me.channel.on("close", async function () { + console.warn("信令通道关闭:", me.address); + me.taoyao.connect = false; + if (me.reconnection) { + me.reconnect(); } // 不要失败回调 }); - self.channel.on("error", async function (e) { - console.error("信令通道异常:", self.address, e); - if (self.reconnection) { - self.reconnect(); + me.channel.on("error", async function (e) { + console.error("信令通道异常:", me.address, e); + me.taoyao.connect = false; + if (me.reconnection) { + me.reconnect(); } // 不要失败回调 }); - self.channel.on("message", async function (data) { + me.channel.on("message", async function (data) { try { const content = data.toString(); console.debug("信令通道消息:", content); - self.callback(JSON.parse(content)); + me.taoyao.on(JSON.parse(content)); } catch (error) { - console.error("处理信令消息异常:", content, error); + console.error("处理信令消息异常:", data, error); } }); }); @@ -173,26 +168,26 @@ const signalChannel = { * 重连 */ reconnect() { - const self = this; + const me = this; if ( - self.lockReconnect || - (self.channel && self.channel.readyState === WebSocket.OPEN) + me.lockReconnect || + (me.channel && me.channel.readyState === WebSocket.OPEN) ) { return; } - self.lockReconnect = true; - if (self.reconnectTimer) { - clearTimeout(self.reconnectTimer); + me.lockReconnect = true; + if (me.reconnectTimer) { + clearTimeout(me.reconnectTimer); } // 定时重连 - self.reconnectTimer = setTimeout(function () { - console.info("信令通道重连:", self.address); - self.connect(self.address, self.callback, self.reconnection); - self.lockReconnect = false; - }, self.connectionTimeout); - self.connectionTimeout = Math.min( - self.connectionTimeout + self.minReconnectionDelay, - self.maxReconnectionDelay + me.reconnectTimer = setTimeout(function () { + console.info("重连信令通道:", me.address); + me.connect(me.address, me.reconnection); + me.lockReconnect = false; + }, me.reconnectionTimeout); + me.reconnectionTimeout = Math.min( + me.reconnectionTimeout + me.minReconnectionDelay, + me.maxReconnectionDelay ); }, /** @@ -200,7 +195,7 @@ const signalChannel = { * * @param {*} message 消息 */ - push(message) { + push(message) { try { this.channel.send(JSON.stringify(message)); } catch (error) { @@ -211,11 +206,11 @@ const signalChannel = { * 关闭通道 */ close() { - const self = this; - self.reconnection = false; - self.channel.close(); - clearTimeout(self.heartbeatTimer); - clearTimeout(self.reconnectTimer); + const me = this; + clearTimeout(me.heartbeatTimer); + clearTimeout(me.reconnectTimer); + me.reconnection = false; + me.channel.close(); }, }; @@ -227,8 +222,8 @@ class Room { close = false; // 房间ID roomId = null; - // 信令 - signal = null; + // 桃夭 + taoyao = null; // WebRTCServer webRtcServer = null; // 路由 @@ -252,7 +247,7 @@ class Room { constructor({ roomId, - signal, + taoyao, webRtcServer, mediasoupRouter, audioLevelObserver, @@ -261,7 +256,7 @@ class Room { this.close = false; this.roomId = roomId; this.networkThrottled = false; - this.signal = signal; + this.taoyao = taoyao; this.webRtcServer = webRtcServer; this.mediasoupRouter = mediasoupRouter; this.audioLevelObserver = audioLevelObserver; @@ -340,9 +335,11 @@ class Room { } /** - * 信令服务 + * 桃夭 */ -class Signal { +class Taoyao { + // 是否连接 + connect = false; // 房间列表 rooms = new Map(); // 回调事件 @@ -588,7 +585,15 @@ class Signal { } async mediaConsume(message, body) { - const { roomId, clientId, sourceId, streamId, producerId, transportId, rtpCapabilities } = body; + const { + roomId, + clientId, + sourceId, + streamId, + producerId, + transportId, + rtpCapabilities, + } = body; const room = this.rooms.get(roomId); const producer = room.producers.get(producerId); const transport = room.transports.get(transportId); @@ -691,20 +696,22 @@ class Signal { ); }); // TODO:改为同步 - this.push(protocol.buildMessage("media::consume", { - //await this.request("media::consume", { - kind: consumer.kind, - type: consumer.type, - roomId: roomId, - clientId: clientId, - sourceId: sourceId, - streamId: streamId, - producerId: producerId, - consumerId: consumer.id, - rtpParameters: consumer.rtpParameters, - appData: producer.appData, - producerPaused: consumer.producerPaused, - })); + this.push( + protocol.buildMessage("media::consume", { + //await this.request("media::consume", { + kind: consumer.kind, + type: consumer.type, + roomId: roomId, + clientId: clientId, + sourceId: sourceId, + streamId: streamId, + producerId: producerId, + consumerId: consumer.id, + rtpParameters: consumer.rtpParameters, + appData: producer.appData, + producerPaused: consumer.producerPaused, + }) + ); await consumer.resume(); this.push( protocol.buildMessage("media::consumer::score", { @@ -883,4 +890,4 @@ class Signal { } } -module.exports = { Signal, signalChannel }; +module.exports = { Taoyao, signalChannel }; diff --git a/taoyao-client-web/src/App.vue b/taoyao-client-web/src/App.vue index 0795c0a..a239654 100644 --- a/taoyao-client-web/src/App.vue +++ b/taoyao-client-web/src/App.vue @@ -10,8 +10,11 @@ v-model="signalVisible" > + + + - + @@ -112,6 +115,7 @@ export default { medias: null, config: { clientId: "taoyao", + name: "taoyao", host: "localhost", port: 8888, username: "taoyao", diff --git a/taoyao-client-web/src/components/Config.js b/taoyao-client-web/src/components/Config.js index 5ec301e..9026d78 100644 --- a/taoyao-client-web/src/components/Config.js +++ b/taoyao-client-web/src/components/Config.js @@ -40,6 +40,20 @@ const defaultVideoConfig = { facingMode: "environment", }; +/** + * VP9默认配置 + */ +const defaultKsvcEncodings = [{ scalabilityMode: "S3T3_KEY" }]; + +/** + * simulcast默认配置 + */ +const defaultSimulcastEncodings = [ + { scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" }, + { scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" }, + { scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" }, +]; + /** * RTCPeerConnection默认配置 */ @@ -56,24 +70,10 @@ const defaultRTCPeerConnectionConfig = { iceCandidatePoolSize: 8, }; -/** - * VP9 - */ -const ksvcEncodings = [{ scalabilityMode: "S3T3_KEY" }]; - -/** - * simulcast - */ -const simulcastEncodings = [ - { scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" }, - { scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" }, - { scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" }, -]; - export { - ksvcEncodings, - simulcastEncodings, defaultAudioConfig, defaultVideoConfig, + defaultKsvcEncodings, + defaultSimulcastEncodings, defaultRTCPeerConnectionConfig, }; diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index 3cf36c2..840cc9f 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -1,12 +1,9 @@ -/** - * 桃夭 - */ import * as mediasoupClient from "mediasoup-client"; import { - ksvcEncodings, - simulcastEncodings, defaultAudioConfig, defaultVideoConfig, + defaultKsvcEncodings, + defaultSimulcastEncodings, defaultRTCPeerConnectionConfig, } from "./Config.js"; @@ -28,17 +25,19 @@ const protocol = { this.index = 0; } const date = new Date(); - return 100000000000000 * date.getDate() + - 1000000000000 * date.getHours() + - 10000000000 * date.getMinutes() + - 100000000 * date.getSeconds() + - 1000 * this.clientIndex + - this.index; + return ( + 100000000000000 * date.getDate() + + 1000000000000 * date.getHours() + + 10000000000 * date.getMinutes() + + 100000000 * date.getSeconds() + + 1000 * this.clientIndex + + this.index + ); }, /** * @param {*} signal 信令标识 * @param {*} body 消息主体 - * @param {*} id 消息标识 + * @param {*} id 消息ID * @param {*} v 消息版本 * * @returns 信令消息 @@ -80,7 +79,7 @@ const signalChannel = { // 防止重复重连 lockReconnect: false, // 当前重连时间 - connectionTimeout: 5 * 1000, + reconnectionTimeout: 5 * 1000, // 最小重连时间 minReconnectionDelay: 5 * 1000, // 最大重连时间 @@ -89,26 +88,24 @@ const signalChannel = { * 心跳 */ heartbeat() { - const self = this; - if (self.heartbeatTimer) { - clearTimeout(self.heartbeatTimer); + const me = this; + if (me.heartbeatTimer) { + clearTimeout(me.heartbeatTimer); } - self.heartbeatTimer = setTimeout(async function () { - if (self.channel && self.channel.readyState === WebSocket.OPEN) { + me.heartbeatTimer = setTimeout(async function () { + if (me.channel && me.channel.readyState === WebSocket.OPEN) { const battery = await navigator.getBattery(); - // TODO:信号强度 - self.push( + me.push( protocol.buildMessage("client::heartbeat", { - signal: 100, battery: battery.level * 100, charging: battery.charging, }) ); - self.heartbeat(); + me.heartbeat(); } else { - console.warn("发送心跳失败:", self.address); + console.warn("心跳失败:", me.address); } - }, self.heartbeatTime); + }, me.heartbeatTime); }, /** * 连接 @@ -119,61 +116,58 @@ const signalChannel = { * @returns Promise */ async connect(address, reconnection = true) { - const self = this; - if (self.channel && self.channel.readyState === WebSocket.OPEN) { + const me = this; + if (me.channel && me.channel.readyState === WebSocket.OPEN) { return new Promise((resolve, reject) => { - resolve(self.channel); + resolve(me.channel); }); } - self.address = address; - self.reconnection = reconnection; + me.address = address; + me.reconnection = reconnection; return new Promise((resolve, reject) => { - console.debug("连接信令通道:", self.address); - self.channel = new WebSocket(self.address); - self.channel.onopen = async function () { - console.debug("打开信令通道:", self.address); - // 注册终端 - // TODO:信号强度 + console.debug("连接信令通道:", me.address); + me.channel = new WebSocket(me.address); + me.channel.onopen = async function () { + console.debug("打开信令通道:", me.address); const battery = await navigator.getBattery(); - self.push( + me.push( protocol.buildMessage("client::register", { - name: "桃夭Web", - clientId: self.taoyao.clientId, + clientId: me.taoyao.clientId, + name: me.taoyao.name, clientType: "WEB", - signal: 100, battery: battery.level * 100, charging: battery.charging, - username: self.taoyao.username, - password: self.taoyao.password, + username: me.taoyao.username, + password: me.taoyao.password, }) ); - // 重置时间 - self.connectionTimeout = self.minReconnectionDelay; - // 开始心跳 - self.heartbeat(); - // 成功回调 - resolve(self.channel); + me.reconnectionTimeout = me.minReconnectionDelay; + me.taoyao.connect = true; + me.heartbeat(); + resolve(me.channel); }; - self.channel.onclose = async function () { - console.warn("信令通道关闭:", self.channel); - if (self.reconnection) { - self.reconnect(); + me.channel.onclose = async function () { + console.warn("信令通道关闭:", me.channel); + me.taoyao.connect = false; + if (me.reconnection) { + me.reconnect(); } // 不要失败回调 }; - self.channel.onerror = async function (e) { - console.error("信令通道异常:", self.channel, e); - if (self.reconnection) { - self.reconnect(); + me.channel.onerror = async function (e) { + console.error("信令通道异常:", me.channel, e); + me.taoyao.connect = false; + if (me.reconnection) { + me.reconnect(); } // 不要失败回调 }; - self.channel.onmessage = async function (e) { - console.debug("信令通道消息:", e.data); + me.channel.onmessage = async function (e) { try { - self.taoyao.on(JSON.parse(e.data)); + console.debug("信令通道消息:", e.data); + me.taoyao.on(JSON.parse(e.data)); } catch (error) { - console.error("处理信令消息异常:", message, error); + console.error("处理信令消息异常:", e, error); } }; }); @@ -182,26 +176,26 @@ const signalChannel = { * 重连 */ reconnect() { - const self = this; + const me = this; if ( - self.lockReconnect || - (self.channel && self.channel.readyState === WebSocket.OPEN) + me.lockReconnect || + (me.channel && me.channel.readyState === WebSocket.OPEN) ) { return; } - self.lockReconnect = true; - if (self.reconnectTimer) { - clearTimeout(self.reconnectTimer); + me.lockReconnect = true; + if (me.reconnectTimer) { + clearTimeout(me.reconnectTimer); } // 定时重连 - self.reconnectTimer = setTimeout(function () { - console.info("信令通道重连:", self.address); - self.connect(self.address, self.reconnection); - self.lockReconnect = false; - }, self.connectionTimeout); - self.connectionTimeout = Math.min( - self.connectionTimeout + self.minReconnectionDelay, - self.maxReconnectionDelay + me.reconnectTimer = setTimeout(function () { + console.info("重连信令通道:", me.address); + me.connect(me.address, me.reconnection); + me.lockReconnect = false; + }, me.reconnectionTimeout); + me.reconnectionTimeout = Math.min( + me.reconnectionTimeout + me.minReconnectionDelay, + me.maxReconnectionDelay ); }, /** @@ -209,8 +203,7 @@ const signalChannel = { * * @param {*} message 消息 */ - push(message, callback) { - // 发送消息 + push(message) { try { signalChannel.channel.send(JSON.stringify(message)); } catch (error) { @@ -221,11 +214,11 @@ const signalChannel = { * 关闭通道 */ close() { - const self = this; - self.reconnection = false; - self.channel.close(); - clearTimeout(self.heartbeatTimer); - clearTimeout(self.reconnectTimer); + const me = this; + clearTimeout(me.heartbeatTimer); + clearTimeout(me.reconnectTimer); + me.reconnection = false; + me.channel.close(); }, }; @@ -233,10 +226,14 @@ const signalChannel = { * 桃夭 */ class Taoyao { + // 信令连接 + connect = false; // 房间标识 roomId; // 终端标识 clientId; + // 终端名称 + name; // 信令地址 host; // 信令端口 @@ -299,6 +296,7 @@ class Taoyao { constructor({ roomId, clientId, + name, host, port, username, @@ -312,6 +310,7 @@ class Taoyao { }) { this.roomId = roomId; this.clientId = clientId; + this.name = name; this.host = host; this.port = port; this.username = username; @@ -338,7 +337,9 @@ class Taoyao { self.callbackMedia = callbackMedia; self.signalChannel = signalChannel; signalChannel.taoyao = self; - return self.signalChannel.connect(`wss://${self.host}:${self.port}/websocket.signal`); + return self.signalChannel.connect( + `wss://${self.host}:${self.port}/websocket.signal` + ); } /** * 异步请求 @@ -476,12 +477,7 @@ class Taoyao { self.audio = { ...defaultAudioConfig, ...message.body.media.audio }; self.video = { ...defaultVideoConfig, ...message.body.media.video }; self.webrtc = message.body.webrtc; - console.debug( - "终端配置", - self.audio, - self.video, - self.webrtc - ); + console.debug("终端配置", self.audio, self.video, self.webrtc); } /** * 终端重启默认回调 @@ -961,9 +957,9 @@ class Taoyao { (this.forceVP9 && codec) || firstVideoCodec.mimeType.toLowerCase() === "video/vp9" ) { - encodings = ksvcEncodings; + encodings = defaultKsvcEncodings; } else { - encodings = simulcastEncodings; + encodings = defaultSimulcastEncodings; } } this.videoProducer = await this.sendTransport.produce({ diff --git a/taoyao-signal-server/README.md b/taoyao-signal-server/README.md index 5f62132..67f2998 100644 --- a/taoyao-signal-server/README.md +++ b/taoyao-signal-server/README.md @@ -13,3 +13,7 @@ [信令格式](https://localhost:8888/protocol/list) +## TODO + +标识 -> ID +所有字段获取 -> get \ No newline at end of file diff --git a/taoyao-signal-server/pom.xml b/taoyao-signal-server/pom.xml index 30fe99b..bbe50f3 100644 --- a/taoyao-signal-server/pom.xml +++ b/taoyao-signal-server/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.2 + 3.0.3 diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Description.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Description.java index e0a5278..36db425 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Description.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Description.java @@ -2,19 +2,18 @@ package com.acgist.taoyao.boot.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.stereotype.Component; - /** * 信令描述 * * @author acgist */ @Target(ElementType.TYPE) -@Component +@Inherited @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Description { @@ -22,7 +21,7 @@ public @interface Description { /** * @return 消息主体 */ - String[] body() default { "{}" }; + String[] body() default { "" }; /** * @return 数据流向 diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java index ea81c87..54ea038 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java @@ -51,6 +51,10 @@ public interface Constant { * 电池电量(0~100) */ String BATTERY = "battery"; + /** + * 是否发生告警 + */ + String ALARMING = "alarming"; /** * 是否正在充电 */ diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebrtcProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebrtcProperties.java index 4d6b510..5b51b82 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebrtcProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebrtcProperties.java @@ -23,6 +23,8 @@ import lombok.Setter; @ConfigurationProperties(prefix = "taoyao.webrtc") public class WebrtcProperties { + @Schema(title = "是否加密", description = "是否加密") + private Boolean encrypt; @Schema(title = "STUN服务器", description = "STUN服务器") private WebrtcStunProperties[] stun; @Schema(title = "TURN服务器", description = "TURN服务器") diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java index c13201f..8294c05 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java @@ -58,8 +58,8 @@ public class SecurityInterceptor extends InterceptorAdapter { if(this.permit(request) || this.authorization(request)) { return true; } - if(log.isInfoEnabled()) { - log.info("授权失败:{}", request.getRequestURL()); + if(log.isDebugEnabled()) { + log.debug("授权失败:{}", request.getRequestURL()); } response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm=\"" + this.securityProperties.getRealm() + "\""); diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml index 0a261ea..797cb8b 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml +++ b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml @@ -17,9 +17,11 @@ server: port-header: X-Forwarded-Port protocol-header: X-Forwarded-Proto remote-ip-header: X-Forwarded-For +# 服务前缀 # servlet: # context-path: /taoyao spring: +# 快速启动 # main: # lazy-initialization: true profiles: @@ -54,10 +56,13 @@ taoyao: name: 桃夭信令服务 version: 1.0.0 description: 桃夭WebRTC信令服务 + # 全局超时时间 timeout: 5000 id: + # 服务端 max-index: 999999 server-index: 0 + # 终端 min-client-index: 10000 max-client-index: 99999 # 媒体配置 @@ -117,6 +122,8 @@ taoyao: buffer-size: 2048 # WebRTC配置 webrtc: + # 是否加密 + encrypt: false # STUN服务 stun: - host: 192.168.1.110 @@ -146,7 +153,7 @@ taoyao: password: taoyao # 定时任务 scheduled: - media: 0 * * * * ? + # 清理无效终端连接 client: 0 * * * * ? # 地址重写 ip-rewrite: diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java index aaf696d..7d5a899 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java @@ -25,7 +25,7 @@ public class ClientStatus { private String ip; @Schema(title = "终端名称", description = "终端名称") private String name; - @Schema(title = "终端标识", description = "终端标识") + @Schema(title = "终端ID", description = "终端ID") private String clientId; @Schema(title = "终端类型", description = "终端类型") private ClientType clientType; @@ -41,6 +41,8 @@ public class ClientStatus { private Integer signal; @Schema(title = "电池电量(0~100)", description = "电池电量(0~100)") private Integer battery; + @Schema(title = "是否发生告警", description = "是否发生告警") + private Boolean alarming; @Schema(title = "是否正在充电", description = "是否正在充电") private Boolean charging; @Schema(title = "是否正在录像", description = "是否正在录像") @@ -64,11 +66,12 @@ public class ClientStatus { this.setTemperature(MapUtils.get(body, Constant.TEMPERATURE)); this.setSignal(MapUtils.get(body, Constant.SIGNAL)); this.setBattery(MapUtils.get(body, Constant.BATTERY)); - this.setCharging(MapUtils.get(body, Constant.CHARGING)); - this.setRecording(MapUtils.get(body, Constant.RECORDING)); - this.setLastHeartbeat(LocalDateTime.now()); + this.setAlarming(MapUtils.getBoolean(body, Constant.ALARMING)); + this.setCharging(MapUtils.getBoolean(body, Constant.CHARGING)); + this.setRecording(MapUtils.getBoolean(body, Constant.RECORDING)); this.status(MapUtils.get(body, Constant.STATUS)); this.config(MapUtils.get(body, Constant.CONFIG)); + this.setLastHeartbeat(LocalDateTime.now()); } /** diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java index 1a3cceb..94ca7e6 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java @@ -3,7 +3,9 @@ package com.acgist.taoyao.signal.controller; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; +import org.springframework.aop.support.AopUtils; import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -74,27 +76,39 @@ public class ProtocolController { final Protocol protocol = e.getValue(); final String name = protocol.name(); final String signal = protocol.signal(); - final Description annotation = protocol.getClass().getDeclaredAnnotation(Description.class); - if(annotation == null) { - log.info("信令没有注解:{}-{}", key, name); + final Class clazz; + final Description description; + if(AopUtils.isAopProxy(e) || AopUtils.isCglibProxy(protocol) || AopUtils.isJdkDynamicProxy(protocol)) { + // 代理获取 + clazz = AopUtils.getTargetClass(protocol); + description = AnnotationUtils.findAnnotation(clazz, Description.class); + } else { + // 直接获取 + clazz = protocol.getClass(); + description = AnnotationUtils.findAnnotation(clazz, Description.class); + } + if(description == null) { + log.info("信令没有注解:{} - {}", key, name); return; } // 信令名称 - builder.append("### ").append(name).append("(").append(signal).append(")") - .append(Constant.LINE).append(Constant.LINE) - .append("```").append(Constant.LINE); - // 消息主体 - builder.append("# 消息主体").append(Constant.LINE); - Stream.of(annotation.body()).forEach(line -> builder.append(line.strip()).append(Constant.LINE)); - // 数据流向 - builder.append("# 数据流向").append(Constant.LINE); - Stream.of(annotation.flow()).forEach(line -> builder.append(line.strip()).append(Constant.LINE)); - builder.append("```").append(Constant.LINE).append(Constant.LINE); + builder.append("### ").append(name) + .append("(").append(signal).append(")") + .append(Constant.LINE).append(Constant.LINE); // 描述信息 - final String memo = annotation.memo().strip(); + final String memo = description.memo().strip(); if(StringUtils.isNotEmpty(memo)) { builder.append(memo).append(Constant.LINE).append(Constant.LINE); } + // 消息主体 + builder + .append("```").append(Constant.LINE) + .append("# 消息主体").append(Constant.LINE); + Stream.of(description.body()).forEach(line -> builder.append(line.strip()).append(Constant.LINE)); + // 数据流向 + builder.append("# 数据流向").append(Constant.LINE); + Stream.of(description.flow()).forEach(line -> builder.append(line.strip()).append(Constant.LINE)); + builder.append("```").append(Constant.LINE).append(Constant.LINE); }); return builder.toString(); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientConfigEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientConfigEvent.java new file mode 100644 index 0000000..8a547bb --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientConfigEvent.java @@ -0,0 +1,23 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * 终端配置事件 + * + * @author acgist + */ +@Getter +@Setter +public class ClientConfigEvent extends ClientEventAdapter { + + private static final long serialVersionUID = 1L; + + public ClientConfigEvent(Client client) { + super(client); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOfflineEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOfflineEvent.java new file mode 100644 index 0000000..9435dd9 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOfflineEvent.java @@ -0,0 +1,23 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * 终端下线事件 + * + * @author acgist + */ +@Getter +@Setter +public class ClientOfflineEvent extends ClientEventAdapter { + + private static final long serialVersionUID = 1L; + + public ClientOfflineEvent(Client client) { + super(client); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOnlineEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOnlineEvent.java new file mode 100644 index 0000000..4034ab6 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientOnlineEvent.java @@ -0,0 +1,23 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * 终端上线事件 + * + * @author acgist + */ +@Getter +@Setter +public class ClientOnlineEvent extends ClientEventAdapter { + + private static final long serialVersionUID = 1L; + + public ClientOnlineEvent(Client client) { + super(client); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomLeaveEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomLeaveEvent.java new file mode 100644 index 0000000..266b62f --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomLeaveEvent.java @@ -0,0 +1,30 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.party.media.Room; + +import lombok.Getter; +import lombok.Setter; + +/** + * 离开房间事件 + * + * @author acgist + */ +@Getter +@Setter +public class RoomLeaveEvent extends RoomEventAdapter { + + private static final long serialVersionUID = 1L; + + /** + * 离开终端 + */ + private final Client client; + + public RoomLeaveEvent(Room room, Client client) { + super(room); + this.client = client; + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/ClientWrapper.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/ClientWrapper.java index 69d22b1..1801320 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/ClientWrapper.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/ClientWrapper.java @@ -16,7 +16,7 @@ import lombok.Setter; */ @Getter @Setter -public class ClientWrapper { +public class ClientWrapper implements AutoCloseable { /** * 媒体订阅类型 @@ -131,5 +131,9 @@ public class ClientWrapper { return this.producers.values().stream() .anyMatch(v -> v.getConsumers().values().stream().anyMatch(c -> c.getProducer() == producer)); } + + @Override + public void close() throws Exception { + } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/Room.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/Room.java index 0b802c7..b3dafea 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/Room.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/media/Room.java @@ -87,14 +87,21 @@ public class Room implements Closeable { /** * 终端离开 + * 立即释放所有资源 * * @param client 终端 */ public void leave(Client client) { synchronized (this.clients) { - if(this.clients.remove(client) != null) { + final ClientWrapper wrapper = this.clients.remove(client); + if(wrapper != null) { + try { + wrapper.close(); + } catch (Exception e) { + log.error("终端关闭异常", e); + } this.roomStatus.setClientSize(this.roomStatus.getClientSize() - 1); - // TODO:资源释放 + // TODO:leave事件 } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java index 2c76690..0b4ad40 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java @@ -81,7 +81,6 @@ public class ProtocolManager { * @param instance 终端实例 */ public void execute(String content, AutoCloseable instance) { - log.debug("执行信令消息:{}", content); final Client client = this.clientManager.clients(instance); if(client == null) { log.warn("信令终端无效:{}-{}", instance, content); @@ -117,6 +116,9 @@ public class ProtocolManager { client.push(this.platformErrorProtocol.build("不支持的信令协议:" + signal)); return; } + if(log.isDebugEnabled()) { + log.debug("执行信令消息:{} - {}", client.clientId(), content); + } if(protocol instanceof ClientRegisterProtocol) { protocol.execute(client, message); } else if(this.securityService.authenticate(client, message, protocol)) { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java index da6036c..aa76de6 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java @@ -8,6 +8,7 @@ import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientStatus; import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @@ -53,6 +54,8 @@ public class ClientAlarmProtocol extends ProtocolClientAdapter { alarmMessage, alarmDatetime ); + final ClientStatus status = client.status(); + status.setAlarming(Boolean.TRUE); // 业务逻辑 } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java index 5053590..614670d 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java @@ -20,16 +20,16 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; */ @Protocol @Description( + memo = "没有指定终端类型时广播所有类型终端", body = { """ { "clientType": "终端类型(可选)" ... - } + } """ }, - flow = "终端->信令服务-)终端", - memo = "没有指定终端类型时广播所有类型终端" + flow = "终端->信令服务-)终端" ) public class ClientBroadcastProtocol extends ProtocolClientAdapter { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java index 098d092..14b4d4c 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java @@ -7,23 +7,24 @@ import org.springframework.scheduling.annotation.Async; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.event.ClientCloseEvent; +import com.acgist.taoyao.signal.event.ClientOfflineEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import lombok.extern.slf4j.Slf4j; /** - * 终端关闭信令 + * 关闭终端信令 * * @author acgist */ @Slf4j @Protocol @Description( + memo = "同时释放所有资源,所以如果终端意外掉线重连,需要终端实现音视频重连逻辑。", flow = { "终端->信令服务->终端", "终端->信令服务-[终端下线])终端" @@ -33,11 +34,8 @@ public class ClientCloseProtocol extends ProtocolClientAdapter implements Applic public static final String SIGNAL = "client::close"; - private final ClientOfflineProtocol clientOfflineProtocol; - - public ClientCloseProtocol(ClientOfflineProtocol clientOfflineProtocol) { - super("终端关闭信令", SIGNAL); - this.clientOfflineProtocol = clientOfflineProtocol; + public ClientCloseProtocol() { + super("关闭终端信令", SIGNAL); } @Async @@ -48,13 +46,12 @@ public class ClientCloseProtocol extends ProtocolClientAdapter implements Applic @Override public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { - // 响应消息 client.push(message.cloneWithoutBody()); - // 关闭连接后会发布事件 try { + // 关闭连接后会发布事件 client.close(); } catch (Exception e) { - log.error("关闭终端异常", e); + log.error("关闭终端异常:{}", clientId, e); } } @@ -63,24 +60,17 @@ public class ClientCloseProtocol extends ProtocolClientAdapter implements Applic * * @param client 终端 */ - public void close(Client client) { - if(!client.authorized()) { + private void close(Client client) { + if(client == null || !client.authorized()) { // 没有授权终端 return; } final String clientId = client.clientId(); log.info("关闭终端:{}", clientId); - // 房间释放 + // 释放房间终端 this.roomManager.leave(client); - // 广播下线事件 - final Message message = this.clientOfflineProtocol.build( - Map.of(Constant.CLIENT_ID, clientId) - ); - this.clientManager.broadcast(clientId, message); - // TODO:释放连接 - // TODO:释放房间 - // TODO:退出帐号 - // TODO:注意释放:是否考虑没有message(非正常的关闭)不要立即释放 + // 终端下线事件 + this.publishEvent(new ClientOfflineEvent(client)); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java index e42a169..229530b 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java @@ -4,6 +4,9 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; @@ -14,6 +17,7 @@ import com.acgist.taoyao.boot.utils.DateUtils; import com.acgist.taoyao.boot.utils.DateUtils.DateTimeStyle; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.ClientConfigEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -30,9 +34,9 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; "datetime": "日期时间(yyyyMMddHHmmss)" } """, - flow = "终端-[终端注册]>信令服务->终端" + flow = "终端=[终端注册]>信令服务->终端" ) -public class ClientConfigProtocol extends ProtocolClientAdapter { +public class ClientConfigProtocol extends ProtocolClientAdapter implements ApplicationListener { public static final String SIGNAL = "client::config"; @@ -45,6 +49,14 @@ public class ClientConfigProtocol extends ProtocolClientAdapter { this.webrtcProperties = webrtcProperties; } + @Async + @Override + public void onApplicationEvent(ClientConfigEvent event) { + final Client client = event.getClient(); + final ClientType clientType = client.clientType(); + client.push(this.build(clientType)); + } + @Override public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { client.push(this.build(clientType)); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java index 716d56a..d62e293 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java @@ -25,6 +25,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "alarming": 是否发生告警(true|false), "charging": 是否正在充电(true|false), "recording": 是否正在录像(true|false), "status": {更多状态}, @@ -43,9 +44,7 @@ public class ClientHeartbeatProtocol extends ProtocolClientAdapter { @Override public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { - // 响应心跳 client.push(message.cloneWithoutBody()); - // 设置状态 final ClientStatus status = client.status(); status.copy(body); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java index 71f125d..a1420bd 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java @@ -20,6 +20,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; */ @Protocol @Description( + memo = "没有选择终端类型时返回所有类型终端状态列表", body = """ { "clientType": "终端类型(可选)" @@ -36,6 +37,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "alarming": 是否发生告警(true|false), "charging": 是否正在充电(true|false), "recording": 是否正在录像(true|false), "status": {更多状态}, diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java index 6dd6901..5b20db1 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java @@ -1,7 +1,14 @@ package com.acgist.taoyao.signal.protocol.client; +import java.util.Map; + +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.signal.event.ClientOfflineEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -18,7 +25,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; """, flow = "终端-[终端关闭]>信令服务-)终端" ) -public class ClientOfflineProtocol extends ProtocolClientAdapter { +public class ClientOfflineProtocol extends ProtocolClientAdapter implements ApplicationListener { public static final String SIGNAL = "client::offline"; @@ -26,4 +33,13 @@ public class ClientOfflineProtocol extends ProtocolClientAdapter { super("终端下线信令", SIGNAL); } + @Async + @Override + public void onApplicationEvent(ClientOfflineEvent event) { + final String clientId = event.getClientId(); + this.clientManager.broadcast(clientId, this.build( + Map.of(Constant.CLIENT_ID, clientId) + )); + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java index e32f339..eb3dda3 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java @@ -1,7 +1,12 @@ package com.acgist.taoyao.signal.protocol.client; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.event.ClientOnlineEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -23,20 +28,32 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "alarming": 是否发生告警(true|false), "charging": 是否正在充电(true|false), "recording": 是否正在录像(true|false), "status": {更多状态}, "config": {更多配置} } """, - flow = "终端-[终端注册]>信令服务-)终端" + flow = "终端=[终端注册]>信令服务-)终端" ) -public class ClientOnlineProtocol extends ProtocolClientAdapter { +public class ClientOnlineProtocol extends ProtocolClientAdapter implements ApplicationListener { public static final String SIGNAL = "client::online"; public ClientOnlineProtocol() { super("终端上线信令", SIGNAL); } + + @Async + @Override + public void onApplicationEvent(ClientOnlineEvent event) { + final Client client = event.getClient(); + final String clientId = event.getClientId(); + this.clientManager.broadcast( + clientId, + this.build(client.status()) + ); + } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java index 8199eec..52c2fc2 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java @@ -8,10 +8,13 @@ import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.CloseableUtils; import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.ClientStatus; import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.ClientConfigEvent; +import com.acgist.taoyao.signal.event.ClientOnlineEvent; import com.acgist.taoyao.signal.event.MediaClientRegisterEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import com.acgist.taoyao.signal.service.SecurityService; @@ -20,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; /** * 终端注册信令 - * 如果需要验证终端授权自行实现 * * @author acgist */ @@ -40,6 +42,7 @@ import lombok.extern.slf4j.Slf4j; "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "alarming": 是否发生告警(true|false), "charging": 是否正在充电(true|false), "recording": 是否正在录像(true|false), "status": {更多状态}, @@ -47,8 +50,9 @@ import lombok.extern.slf4j.Slf4j; } """, flow = { - "终端->信令服务->终端", - "终端->信令服务-[终端上线])终端" + "终端=>信令服务->终端", + "终端=>信令服务-[终端配置]>终端", + "终端=>信令服务-[终端上线])终端" } ) public class ClientRegisterProtocol extends ProtocolClientAdapter { @@ -56,14 +60,10 @@ public class ClientRegisterProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "client::register"; private final SecurityService securityService; - private final ClientConfigProtocol clientConfigProtocol; - private final ClientOnlineProtocol clientOnlineProtocol; - public ClientRegisterProtocol(SecurityService securityService, ClientConfigProtocol clientConfigProtocol, ClientOnlineProtocol clientOnlineProtocol) { + public ClientRegisterProtocol(SecurityService securityService) { super("终端注册信令", SIGNAL); this.securityService = securityService; - this.clientConfigProtocol = clientConfigProtocol; - this.clientOnlineProtocol = clientOnlineProtocol; } @Override @@ -71,12 +71,11 @@ public class ClientRegisterProtocol extends ProtocolClientAdapter { final String clientId = MapUtils.get(body, Constant.CLIENT_ID); final String username = MapUtils.get(body, Constant.USERNAME); final String password = MapUtils.get(body, Constant.PASSWORD); - // 如果需要终端鉴权在此实现 if(this.securityService.authenticate(username, password)) { final Client oldClient = this.clientManager.clients(clientId); if(oldClient != null) { log.debug("终端已经存在(注销旧的终端):{}", clientId); - oldClient.clientId(); + CloseableUtils.close(oldClient); } log.info("终端注册:{}", clientId); client.authorize(clientId); @@ -85,20 +84,17 @@ public class ClientRegisterProtocol extends ProtocolClientAdapter { throw MessageCodeException.of(MessageCode.CODE_3401, "注册失败"); } final ClientType clientType = ClientType.of(MapUtils.get(body, Constant.CLIENT_TYPE)); - // 推送消息 - final Message registerResponse = message.cloneWithoutBody(); - registerResponse.setBody(Map.of(Constant.INDEX, this.idService.buildClientIndex())); - client.push(registerResponse); - // 下发配置 - client.push(this.clientConfigProtocol.build(clientType)); - // 终端状态 - final ClientStatus status = this.buildStatus(clientId, clientType, client, body); - // 上线事件 - this.clientManager.broadcast( - clientId, - this.clientOnlineProtocol.build(status) - ); - // 媒体服务终端注册 + // 注册响应消息 + final Message response = message.cloneWithoutBody(); + response.setBody(Map.of(Constant.INDEX, this.idService.buildClientIndex())); + client.push(response); + // 设置终端状态 + this.buildStatus(clientId, clientType, client, body); + // 终端配置事件 + this.publishEvent(new ClientConfigEvent(client)); + // 终端上线事件 + this.publishEvent(new ClientOnlineEvent(client)); + // 媒体服务注册事件 if(clientType == ClientType.MEDIA) { this.publishEvent(new MediaClientRegisterEvent(client)); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java index c559d2a..248b75c 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java @@ -16,11 +16,11 @@ import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter; */ @Protocol @Description( + memo = "状态通过心跳回传", flow = { "信令服务->终端", "终端->信令服务->终端" - }, - memo = "状态通过心跳回传" + } ) public class ControlRecordProtocol extends ProtocolControlAdapter { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomBroadcastProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomBroadcastProtocol.java new file mode 100644 index 0000000..2be8a34 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomBroadcastProtocol.java @@ -0,0 +1,40 @@ +package com.acgist.taoyao.signal.protocol.room; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.party.media.Room; +import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; + +/** + * 房间广播信令 + * + * @author acgist + */ +@Protocol +@Description( + body = """ + { + ... + } + """, + flow = "终端->信令服务->终端" +) +public class RoomBroadcastProtocol extends ProtocolRoomAdapter { + + public static final String SIGNAL = "room::broadcast"; + + protected RoomBroadcastProtocol() { + super("房间广播信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + room.broadcast(client, message); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java index 9ba13db..71fb86e 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java @@ -31,6 +31,7 @@ public class RoomCloseProtocol extends ProtocolRoomAdapter { public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { room.close(); this.clientManager.broadcast(message); + // TODO:释放房间 } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java index cb5f84e..d799fa8 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java @@ -1,10 +1,66 @@ package com.acgist.taoyao.signal.protocol.room; +import java.util.Map; + +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.RoomLeaveEvent; +import com.acgist.taoyao.signal.party.media.Room; +import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; + /** * 离开房间信令 * * @author acgist */ -public class RoomLeaveProtocol { +@Protocol +@Description( + body = """ + { + "clientId": "离开终端ID" + } + """, + flow = { + "终端->信令服务-)终端", + "终端-[关闭终端]>信令服务-)终端", + } +) +public class RoomLeaveProtocol extends ProtocolRoomAdapter implements ApplicationListener { + + public static final String SIGNAL = "room::leave"; + + public RoomLeaveProtocol() { + super("离开房间信令", SIGNAL); + } + + @Async + @Override + public void onApplicationEvent(RoomLeaveEvent event) { + this.leave(event.getRoom(), event.getClient()); + } + + @Override + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + // 离开房间后会发布事件 + room.leave(client); + } + + /** + * 离开房间 + * + * @param room 房间 + * @param client 终端 + */ + private void leave(Room room, Client client) { + final Message leaveMessage = this.build(Map.of(Constant.CLIENT_ID, client.clientId())); + room.broadcast(client, leaveMessage); + } }