[+] 优化房间创建、关闭、广播、终端列表逻辑

现在已经能够拉出视频
This commit is contained in:
acgist
2023-03-04 23:33:40 +08:00
parent 07678b3297
commit 0f23156df1
21 changed files with 317 additions and 140 deletions

View File

@@ -22,6 +22,8 @@ module.exports = {
name: "桃夭媒体服务", name: "桃夭媒体服务",
// 地址 // 地址
host: "127.0.0.1", host: "127.0.0.1",
// host: "192.168.1.100",
// host: "192.168.8.100",
// 端口 // 端口
port: 8888, port: 8888,
// 协议 // 协议

View File

@@ -18,7 +18,7 @@ const taoyao = new Taoyao(mediasoupWorkers);
* 创建Mediasoup Worker列表 * 创建Mediasoup Worker列表
*/ */
async function buildMediasoupWorkers() { async function buildMediasoupWorkers() {
// 可配置的事件 // 监听事件
// mediasoup.observer.on("newworker", fn(worker)); // mediasoup.observer.on("newworker", fn(worker));
const { workerSize } = config.mediasoup; const { workerSize } = config.mediasoup;
console.info("创建Mediasoup Worker数量", workerSize); console.info("创建Mediasoup Worker数量", workerSize);
@@ -29,16 +29,6 @@ async function buildMediasoupWorkers() {
rtcMinPort: Number(config.mediasoup.workerSettings.rtcMinPort), rtcMinPort: Number(config.mediasoup.workerSettings.rtcMinPort),
rtcMaxPort: Number(config.mediasoup.workerSettings.rtcMaxPort), rtcMaxPort: Number(config.mediasoup.workerSettings.rtcMaxPort),
}); });
worker.on("died", (error) => {
console.warn("Mediasoup Worker停止服务", worker.pid, error);
setTimeout(() => process.exit(1), 2000);
});
worker.observer.on("close", () => {
console.warn("Mediasoup Worker关闭服务", worker.pid);
});
// 可配置的事件
// worker.observer.on("newrouter", fn(router));
// worker.observer.on("newwebrtcserver", fn(router));
// 配置WebRTC服务 // 配置WebRTC服务
const webRtcServerOptions = JSON.parse( const webRtcServerOptions = JSON.parse(
JSON.stringify(config.mediasoup.webRtcServerOptions) JSON.stringify(config.mediasoup.webRtcServerOptions)
@@ -49,6 +39,16 @@ async function buildMediasoupWorkers() {
const webRtcServer = await worker.createWebRtcServer(webRtcServerOptions); const webRtcServer = await worker.createWebRtcServer(webRtcServerOptions);
worker.appData.webRtcServer = webRtcServer; worker.appData.webRtcServer = webRtcServer;
mediasoupWorkers.push(worker); mediasoupWorkers.push(worker);
// 监听事件
worker.on("died", (error) => {
console.warn("Mediasoup Worker停止服务", worker.pid, error);
setTimeout(() => process.exit(1), 2000);
});
worker.observer.on("close", () => {
console.warn("Mediasoup Worker关闭服务", worker.pid);
});
// worker.observer.on("newrouter", fn(router));
// worker.observer.on("newwebrtcserver", fn(router));
} }
} }

View File

@@ -159,7 +159,7 @@ const signalChannel = {
console.debug("信令通道消息:", content); console.debug("信令通道消息:", content);
me.taoyao.on(JSON.parse(content)); me.taoyao.on(JSON.parse(content));
} catch (error) { } catch (error) {
console.error("处理信令消息异常:", data, error); console.error("处理信令消息异常:", data.toString(), error);
} }
}); });
}); });
@@ -322,15 +322,18 @@ class Room {
/** /**
* 关闭资源 * 关闭资源
*/ */
close() { closeAll() {
const self = this; const me = this;
if (self.close) { if (me.close) {
return; return;
} }
self.close = true; me.close = true;
if (self.mediasoupRouter) { // me.producers.forEach(v => v.close());
self.mediasoupRouter.close(); // me.consumers.forEach(v => v.close());
} // me.dataProducers.forEach(v => v.close());
// me.dataConsumers.forEach(v => v.close());
me.transports.forEach(v => v.close());
me.mediasoupRouter.close();
} }
} }
@@ -404,6 +407,9 @@ class Taoyao {
case "room::create": case "room::create":
this.roomCreate(message, body); this.roomCreate(message, body);
break; break;
case "room::close":
this.roomClose(message, body);
break;
} }
} }
@@ -812,8 +818,9 @@ class Taoyao {
); );
}); });
await transport.enableTraceEvent(["bwe"]); await transport.enableTraceEvent(["bwe"]);
// await transport.enableTraceEvent([ 'probation', 'bwe' ]);
transport.on("trace", (trace) => { transport.on("trace", (trace) => {
console.debug("transport trace event", trace, trace.type, transport.id); console.debug("transport trace event", transport.id, trace.type, trace);
}); });
// 可配置的事件 // 可配置的事件
// transport.on("routerclose", fn()); // transport.on("routerclose", fn());
@@ -842,30 +849,42 @@ class Taoyao {
} }
} }
/**
* 关闭房间信令
*
* @param {*} message 消息
* @param {*} body 消息主体
*/
async roomClose(message, body) {
const roomId = body.roomId;
const room = this.rooms.get(roomId);
if(!room) {
console.warn("房间无效:", roomId);
return;
}
console.info("关闭房间:", roomId);
room.closeAll();
this.rooms.delete(roomId);
}
/** /**
* 创建房间信令 * 创建房间信令
* *
* @param {*} message 消息 * @param {*} message 消息
* @param {*} body 消息主体 * @param {*} body 消息主体
*
* @returns 房间
*/ */
async roomCreate(message, body) { async roomCreate(message, body) {
const me = this;
const roomId = body.roomId; const roomId = body.roomId;
let room = this.rooms.get(roomId); let room = me.rooms.get(roomId);
if (room) { if (room) {
this.push(message); console.debug("创建房间已经存在:", room);
return room; me.push(message);
return;
} }
const mediasoupWorker = this.nextMediasoupWorker(); const mediasoupWorker = me.nextMediasoupWorker();
const { mediaCodecs } = config.mediasoup.routerOptions; const { mediaCodecs } = config.mediasoup.routerOptions;
const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs }); const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs });
mediasoupRouter.observer.on("close", () => {
// TODO通知房间关闭
});
// 可配置的事件
// mediasoupRouter.on("workerclose", () => {});
// mediasoupRouter.observer.on("newtransport", fn(transport));
// TODO下面两个监控改为配置启用 // TODO下面两个监控改为配置启用
const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver({ const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver({
maxEntries: 1, maxEntries: 1,
@@ -883,10 +902,22 @@ class Taoyao {
audioLevelObserver, audioLevelObserver,
activeSpeakerObserver, activeSpeakerObserver,
}); });
this.rooms.set(roomId, room); me.rooms.set(roomId, room);
console.info("创建房间", roomId); console.info("创建房间", roomId);
this.push(message); me.push(message);
return room; // 监听事件
mediasoupRouter.observer.on("close", () => {
console.info("房间路由关闭:", roomId, mediasoupRouter);
room.closeAll();
me.rooms.delete(roomId);
me.push(
protocol.buildMessage("room::close", {
roomId: roomId
})
);
});
// mediasoupRouter.on("workerclose", () => {});
// mediasoupRouter.observer.on("newtransport", fn(transport));
} }
} }

View File

@@ -59,7 +59,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="创建房间" name="create"> <el-tab-pane label="创建房间" name="create">
<el-form-item label="媒体服务"> <el-form-item label="媒体服务">
<el-select v-model="room.mediaId" placeholder="媒体服务标识"> <el-select v-model="room.mediaClientId" placeholder="媒体服务标识">
<el-option <el-option
v-for="value in medias" v-for="value in medias"
:key="value.clientId" :key="value.clientId"
@@ -79,7 +79,7 @@
</el-form> </el-form>
<template #footer> <template #footer>
<el-button type="primary" @click="enterRoom" v-if="roomActive === 'enter'">进入</el-button> <el-button type="primary" @click="enterRoom" v-if="roomActive === 'enter'">进入</el-button>
<el-button type="primary" @click="createRoom" v-if="roomActive === 'create'">创建</el-button> <el-button type="primary" @click="roomCreate" v-if="roomActive === 'create'">创建</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -90,7 +90,7 @@
<el-button type="primary" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button> <el-button type="primary" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button>
<el-button>邀请终端</el-button> <el-button>邀请终端</el-button>
<el-button>退出房间</el-button> <el-button>退出房间</el-button>
<el-button type="danger">关闭房间</el-button> <el-button @click="closeRoom()" type="danger">关闭房间</el-button>
</div> </div>
<!-- 终端 --> <!-- 终端 -->
@@ -149,14 +149,17 @@ export default {
this.medias = await this.taoyao.mediaList(); this.medias = await this.taoyao.mediaList();
}, },
async enterRoom() { async enterRoom() {
await this.taoyao.enterRoom(this.room.roomId); await this.taoyao.enterRoom(this.room.roomId, this.room.password);
await this.taoyao.produceMedia(); await this.taoyao.produceMedia();
this.roomVisible = false; this.roomVisible = false;
}, },
async createRoom() { async roomCreate() {
const room = await this.taoyao.createRoom(this.room); const room = await this.taoyao.roomCreate(this.room);
this.room = room; this.room.roomId = room.roomId;
await this.enterRoom(room.roomId); await this.enterRoom();
},
async closeRoom() {
this.taoyao.closeRoom();
}, },
/** /**
* 信令回调 * 信令回调

View File

@@ -394,6 +394,7 @@ class Taoyao {
}, 5000); }, 5000);
}); });
} }
/************************ 回调 ************************/
/** /**
* 回调策略: * 回调策略:
* 1. 如果注册请求回调同时执行结果返回true不再执行后面所有回调。 * 1. 如果注册请求回调同时执行结果返回true不再执行后面所有回调。
@@ -450,25 +451,32 @@ class Taoyao {
* @param {*} message 消息 * @param {*} message 消息
*/ */
async postCallback(message) { async postCallback(message) {
const self = this; const me = this;
switch (message.header.signal) { switch (message.header.signal) {
case "room::client::list":
me.defaultRoomClientList(message);
break;
case "client::reboot": case "client::reboot":
self.defaultClientReboot(message); me.defaultClientReboot(message);
break; break;
case "client::shutdown": case "client::shutdown":
self.defaultClientShutdown(message); me.defaultClientShutdown(message);
break;
case "room::close":
me.defaultRoomClose(message);
break; break;
case "room::enter": case "room::enter":
self.defaultRoomEnter(message); me.defaultRoomEnter(message);
break;
case "room::client::list":
self.defaultRoomClientList(message);
break; break;
case "platform::error": case "platform::error":
self.callbackError(message); me.callbackError(message);
break;
default:
console.warn("不支持的信令:", message);
break; break;
} }
} }
/************************ 信令 ************************/
/** /**
* 配置默认回调 * 配置默认回调
* *
@@ -505,6 +513,53 @@ class Taoyao {
console.info("关闭终端"); console.info("关闭终端");
window.close(); window.close();
} }
/**
* 房间终端列表信令
*
* @param {*} message 消息
*/
defaultRoomClientList(message) {
const me = this;
message.body.forEach(v => {
if (v.clientId === me.clientId) {
// 忽略自己
} else {
me.remoteClients.set(v.clientId, me.roomId);
}
});
}
/**
* 关闭房间信令
*
* @param {*} message 消息
*/
defaultRoomClose(message) {
const me = this;
const { roomId } = message.body;
if(me.roomId !== roomId) {
return;
}
console.info("关闭房间:", roomId);
me.close();
}
/**
* 创建房间信令
*
* @param {*} room 房间
*
* @returns 房间
*/
async roomCreate(room) {
const me = this;
if (!room) {
me.callbackError("无效房间");
return;
}
const response = await me.request(
protocol.buildMessage("room::create", room)
);
return response.body;
}
defaultRoomEnter(message) { defaultRoomEnter(message) {
const { roomId, clientId } = message.body; const { roomId, clientId } = message.body;
if (clientId === this.clientId) { if (clientId === this.clientId) {
@@ -513,16 +568,6 @@ class Taoyao {
this.remoteClients.set(clientId, roomId); this.remoteClients.set(clientId, roomId);
} }
} }
defaultRoomClientList(message) {
const self = this;
message.body.forEach((v) => {
if (v.clientId === self.clientId) {
// 忽略自己
} else {
self.remoteClients.set(v.clientId, self.roomId);
}
});
}
/** /**
* 错误回调 * 错误回调
*/ */
@@ -562,21 +607,7 @@ class Taoyao {
); );
return response.body; return response.body;
} }
/** async enterRoom(roomId, password) {
* 创建房间
*/
async createRoom(room) {
const self = this;
if (!room) {
this.callbackError("无效房间");
return;
}
const response = await self.request(
protocol.buildMessage("room::create", room)
);
return response.body;
}
async enterRoom(roomId) {
const self = this; const self = this;
if (!roomId) { if (!roomId) {
this.callbackError("无效房间"); this.callbackError("无效房间");
@@ -586,7 +617,7 @@ class Taoyao {
self.mediasoupDevice = new mediasoupClient.Device(); self.mediasoupDevice = new mediasoupClient.Device();
const response = await self.request( const response = await self.request(
protocol.buildMessage("media::router::rtp::capabilities", { protocol.buildMessage("media::router::rtp::capabilities", {
roomId: self.roomId, roomId: self.roomId
}) })
); );
const routerRtpCapabilities = response.body.rtpCapabilities; const routerRtpCapabilities = response.body.rtpCapabilities;
@@ -594,6 +625,7 @@ class Taoyao {
await self.request( await self.request(
protocol.buildMessage("room::enter", { protocol.buildMessage("room::enter", {
roomId: roomId, roomId: roomId,
password: password,
rtpCapabilities: self.consume rtpCapabilities: self.consume
? self.mediasoupDevice.rtpCapabilities ? self.mediasoupDevice.rtpCapabilities
: undefined, : undefined,
@@ -604,6 +636,17 @@ class Taoyao {
}) })
); );
} }
async closeRoom() {
const me = this;
if(!me.roomId) {
console.warn("房间无效:", me.roomId);
return;
}
me.push(protocol.buildMessage("room::close", {
roomId: me.roomId
}));
}
/************************ 媒体 ************************/
/** /**
* 生产媒体 * 生产媒体
*/ */

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from "node:url";
import fs from "node:fs";
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],

View File

@@ -143,10 +143,6 @@ public interface Constant {
* 房间ID * 房间ID
*/ */
String ROOM_ID = "roomId"; String ROOM_ID = "roomId";
/**
* 媒体服务ID
*/
String MEDIA_ID = "mediaId";
/** /**
* 媒体流ID * 媒体流ID
*/ */
@@ -179,6 +175,10 @@ public interface Constant {
* 数据消费者ID * 数据消费者ID
*/ */
String DATA_CONSUMER_ID = "dataConsumerId"; String DATA_CONSUMER_ID = "dataConsumerId";
/**
* 媒体服务ID
*/
String MEDIA_CLIENT_ID = "mediaClientId";
/** /**
* ICE服务 * ICE服务
*/ */

View File

@@ -162,11 +162,11 @@ public class BootAutoConfiguration {
public void init() { public void init() {
final Runtime runtime = Runtime.getRuntime(); final Runtime runtime = Runtime.getRuntime();
final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
final String maxMemory = FileUtils.formatSize(runtime.maxMemory());
final String freeMemory = FileUtils.formatSize(runtime.freeMemory()); final String freeMemory = FileUtils.formatSize(runtime.freeMemory());
final String totalMemory = FileUtils.formatSize(runtime.totalMemory()); final String totalMemory = FileUtils.formatSize(runtime.totalMemory());
final String maxMemory = FileUtils.formatSize(runtime.maxMemory());
log.info("操作系统名称:{}", System.getProperty("os.name"));
log.info("操作系统架构:{}", System.getProperty("os.arch")); log.info("操作系统架构:{}", System.getProperty("os.arch"));
log.info("操作系统名称:{}", System.getProperty("os.name"));
log.info("操作系统版本:{}", System.getProperty("os.version")); log.info("操作系统版本:{}", System.getProperty("os.version"));
log.info("可用的处理器数量:{}", runtime.availableProcessors()); log.info("可用的处理器数量:{}", runtime.availableProcessors());
log.info("Java版本{}", System.getProperty("java.version")); log.info("Java版本{}", System.getProperty("java.version"));
@@ -175,9 +175,9 @@ public class BootAutoConfiguration {
log.info("ClassPath{}", System.getProperty("java.class.path")); log.info("ClassPath{}", System.getProperty("java.class.path"));
log.info("虚拟机名称:{}", System.getProperty("java.vm.name")); log.info("虚拟机名称:{}", System.getProperty("java.vm.name"));
log.info("虚拟机参数:{}", runtimeMXBean.getInputArguments().stream().collect(Collectors.joining(" "))); log.info("虚拟机参数:{}", runtimeMXBean.getInputArguments().stream().collect(Collectors.joining(" ")));
log.info("虚拟机最大内存:{}", maxMemory);
log.info("虚拟机空闲内存:{}", freeMemory); log.info("虚拟机空闲内存:{}", freeMemory);
log.info("虚拟机已用内存:{}", totalMemory); log.info("虚拟机已用内存:{}", totalMemory);
log.info("虚拟机最大内存:{}", maxMemory);
log.info("工作目录:{}", System.getProperty("user.dir")); log.info("工作目录:{}", System.getProperty("user.dir"));
log.info("用户目录:{}", System.getProperty("user.home")); log.info("用户目录:{}", System.getProperty("user.home"));
log.info("临时目录:{}", System.getProperty("java.io.tmpdir")); log.info("临时目录:{}", System.getProperty("java.io.tmpdir"));

View File

@@ -1,5 +1,6 @@
package com.acgist.taoyao.boot.utils; package com.acgist.taoyao.boot.utils;
import java.math.BigDecimal;
import java.util.Map; import java.util.Map;
/** /**
@@ -61,8 +62,35 @@ public final class MapUtils {
return null; return null;
} else if(object instanceof Long value) { } else if(object instanceof Long value) {
return value; return value;
} else if(object instanceof Integer value) {
return value.longValue();
} else if(object instanceof Double value) {
return value.longValue();
} }
return Long.valueOf(object.toString()); return new BigDecimal(object.toString()).longValue();
}
/**
* @param body 消息主体
* @param key 参数名称
*
* @return 参数值
*/
public static final Double getDouble(Map<?, ?> body, String key) {
if(body == null) {
return null;
}
final Object object = body.get(key);
if(object == null) {
return null;
} else if(object instanceof Long value) {
return value.doubleValue();
} else if(object instanceof Integer value) {
return value.doubleValue();
} else if(object instanceof Double value) {
return value;
}
return new BigDecimal(object.toString()).doubleValue();
} }
/** /**
@@ -78,10 +106,14 @@ public final class MapUtils {
final Object object = body.get(key); final Object object = body.get(key);
if(object == null) { if(object == null) {
return null; return null;
} else if(object instanceof Long value) {
return value.intValue();
} else if(object instanceof Integer value) { } else if(object instanceof Integer value) {
return value; return value;
} else if(object instanceof Double value) {
return value.intValue();
} }
return Integer.valueOf(object.toString()); return new BigDecimal(object.toString()).intValue();
} }
/** /**

View File

@@ -60,12 +60,12 @@ public class ClientStatus {
* @param body 消息主体 * @param body 消息主体
*/ */
public void copy(Map<String, Object> body) { public void copy(Map<String, Object> body) {
this.setLatitude(MapUtils.get(body, Constant.LATITUDE)); this.setLatitude(MapUtils.getDouble(body, Constant.LATITUDE));
this.setLongitude(MapUtils.get(body, Constant.LONGITUDE)); this.setLongitude(MapUtils.getDouble(body, Constant.LONGITUDE));
this.setHumidity(MapUtils.get(body, Constant.HUMIDITY)); this.setHumidity(MapUtils.getDouble(body, Constant.HUMIDITY));
this.setTemperature(MapUtils.get(body, Constant.TEMPERATURE)); this.setTemperature(MapUtils.getDouble(body, Constant.TEMPERATURE));
this.setSignal(MapUtils.get(body, Constant.SIGNAL)); this.setSignal(MapUtils.getInteger(body, Constant.SIGNAL));
this.setBattery(MapUtils.get(body, Constant.BATTERY)); this.setBattery(MapUtils.getInteger(body, Constant.BATTERY));
this.setAlarming(MapUtils.getBoolean(body, Constant.ALARMING)); this.setAlarming(MapUtils.getBoolean(body, Constant.ALARMING));
this.setCharging(MapUtils.getBoolean(body, Constant.CHARGING)); this.setCharging(MapUtils.getBoolean(body, Constant.CHARGING));
this.setRecording(MapUtils.getBoolean(body, Constant.RECORDING)); this.setRecording(MapUtils.getBoolean(body, Constant.RECORDING));

View File

@@ -196,7 +196,7 @@ public class Room implements Closeable {
@Override @Override
public void close() { public void close() {
log.info("关闭房间:{}", this.roomId); log.info("关闭房间:{}", this.roomId);
// TODO // TODO:关闭房间
} }
} }

View File

@@ -40,7 +40,7 @@ public class MediaConsumeProtocol extends ProtocolRoomAdapter implements Applica
public static final String SIGNAL = "media::consume"; public static final String SIGNAL = "media::consume";
protected MediaConsumeProtocol() { public MediaConsumeProtocol() {
super("消费媒体信令", SIGNAL); super("消费媒体信令", SIGNAL);
} }

View File

@@ -51,7 +51,7 @@ public class MediaTransportWebRtcCreateProtocol extends ProtocolRoomAdapter {
public static final String SIGNAL = "media::transport::webrtc::create"; public static final String SIGNAL = "media::transport::webrtc::create";
protected MediaTransportWebRtcCreateProtocol() { public MediaTransportWebRtcCreateProtocol() {
super("创建WebRTC通道信令", SIGNAL); super("创建WebRTC通道信令", SIGNAL);
} }

View File

@@ -32,7 +32,7 @@ import lombok.extern.slf4j.Slf4j;
} }
""" """
}, },
flow = "终端->服务->终端" flow = "终端->信令服务->终端"
) )
public class PlatformScriptProtocol extends ProtocolClientAdapter { public class PlatformScriptProtocol extends ProtocolClientAdapter {

View File

@@ -62,7 +62,7 @@ public class PlatformShutdownProtocol extends ProtocolClientAdapter implements C
if(this.applicationContext instanceof ConfigurableApplicationContext context) { if(this.applicationContext instanceof ConfigurableApplicationContext context) {
// API关闭 // API关闭
if(context.isActive()) { if(context.isActive()) {
// 如果需要完整广播可以设置延时 // 如果需要广播完成可以设置延时
context.close(); context.close();
} else { } else {
// 其他情况 // 其他情况

View File

@@ -19,16 +19,17 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
@Description( @Description(
body = """ body = """
{ {
"roomId": "房间ID",
... ...
} }
""", """,
flow = "终端->信令服务->终端" flow = "终端->信令服务-)终端"
) )
public class RoomBroadcastProtocol extends ProtocolRoomAdapter { public class RoomBroadcastProtocol extends ProtocolRoomAdapter {
public static final String SIGNAL = "room::broadcast"; public static final String SIGNAL = "room::broadcast";
protected RoomBroadcastProtocol() { public RoomBroadcastProtocol() {
super("房间广播信令", SIGNAL); super("房间广播信令", SIGNAL);
} }

View File

@@ -17,6 +17,36 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
*/ */
@Protocol @Protocol
@Description( @Description(
body = {
"""
{
"roomId": "房间ID"
}
""",
"""
[
{
"ip": "终端IP",
"name": "终端名称",
"clientId": "终端ID",
"clientType": "终端类型",
"latitude": 纬度,
"longitude": 经度,
"humidity": 湿度,
"temperature": 温度,
"signal": 信号强度0~100,
"battery": 电池电量0~100,
"alarming": 是否发生告警true|false,
"charging": 是否正在充电true|false,
"recording": 是否正在录像true|false,
"lastHeartbeat": "最后心跳时间",
"status": {更多状态},
"config": {更多配置}
},
...
]
"""
},
flow = "终端=>信令服务->终端" flow = "终端=>信令服务->终端"
) )
public class RoomClientListProtocol extends ProtocolRoomAdapter { public class RoomClientListProtocol extends ProtocolRoomAdapter {

View File

@@ -4,6 +4,7 @@ import java.util.Map;
import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Description;
import com.acgist.taoyao.boot.annotation.Protocol; 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.boot.model.Message;
import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.Client;
import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.client.ClientType;
@@ -17,6 +18,11 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
*/ */
@Protocol @Protocol
@Description( @Description(
body = """
{
"roomId": "房间ID"
}
""",
flow = "终端->信令服务->媒体服务->信令服务+)终端" flow = "终端->信令服务->媒体服务->信令服务+)终端"
) )
public class RoomCloseProtocol extends ProtocolRoomAdapter { public class RoomCloseProtocol extends ProtocolRoomAdapter {
@@ -29,9 +35,15 @@ public class RoomCloseProtocol extends ProtocolRoomAdapter {
@Override @Override
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) { public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
room.close(); if(clientType == ClientType.WEB) {
this.clientManager.broadcast(message); mediaClient.push(this.build(Map.of(Constant.ROOM_ID, room.getRoomId())));
// TODO释放房间 } else if(clientType == ClientType.MEDIA) {
room.close();
room.broadcast(message);
// TODO移除
} else {
this.logNoAdapter(clientType);
}
} }
} }

View File

@@ -16,25 +16,30 @@ import com.acgist.taoyao.signal.event.MediaClientRegisterEvent;
import com.acgist.taoyao.signal.party.media.Room; import com.acgist.taoyao.signal.party.media.Room;
import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter;
import lombok.extern.slf4j.Slf4j;
/** /**
* 创建房间信令 * 创建房间信令
* *
* @author acgist * @author acgist
*/ */
@Slf4j
@Protocol @Protocol
@Description( @Description(
body = """ body = {
{ """
"name": "房间名称", {
"passowrd": "房间密码", "name": "房间名称",
"mediaId": "媒体服务标识", "passowrd": "房间密码(选填)",
"clientSize": "终端数量" "mediaClientId": "媒体服务ID"
} }
""", """,
flow = "终端->服务端+)终端" """
{
"name": "房间名称",
"clientSize": "终端数量",
"mediaClientId": "媒体服务ID"
}
"""
},
flow = "终端->信令服务->媒体服务->信令服务+)终端"
) )
public class RoomCreateProtocol extends ProtocolClientAdapter implements ApplicationListener<MediaClientRegisterEvent> { public class RoomCreateProtocol extends ProtocolClientAdapter implements ApplicationListener<MediaClientRegisterEvent> {
@@ -53,19 +58,13 @@ public class RoomCreateProtocol extends ProtocolClientAdapter implements Applica
@Override @Override
public void execute(String clientId, ClientType clientType, Client client, Message message, Map<String, Object> body) { public void execute(String clientId, ClientType clientType, Client client, Message message, Map<String, Object> body) {
if(clientType == ClientType.WEB) { if(clientType == ClientType.WEB) {
final String roomId = MapUtils.get(body, Constant.ROOM_ID); // WEB同步创建房间
if (roomId != null && this.roomManager.room(roomId) != null) {
log.info("房间已经存在:{}", roomId);
return;
}
// 创建房间
final Room room = this.roomManager.create( final Room room = this.roomManager.create(
MapUtils.get(body, Constant.NAME), MapUtils.get(body, Constant.NAME),
MapUtils.get(body, Constant.PASSWORD), MapUtils.get(body, Constant.PASSWORD),
MapUtils.get(body, Constant.MEDIA_ID), MapUtils.get(body, Constant.MEDIA_CLIENT_ID),
message.cloneWithoutBody() message.cloneWithoutBody()
); );
// 广播消息
message.setBody(room.getRoomStatus()); message.setBody(room.getRoomStatus());
this.clientManager.broadcast(message); this.clientManager.broadcast(message);
} else { } else {

View File

@@ -2,6 +2,8 @@ package com.acgist.taoyao.signal.protocol.room;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Description;
import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.annotation.Protocol;
import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.config.Constant;
@@ -12,8 +14,8 @@ import com.acgist.taoyao.boot.utils.MapUtils;
import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.Client;
import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.client.ClientType;
import com.acgist.taoyao.signal.party.media.ClientWrapper; import com.acgist.taoyao.signal.party.media.ClientWrapper;
import com.acgist.taoyao.signal.party.media.Room;
import com.acgist.taoyao.signal.party.media.ClientWrapper.SubscribeType; import com.acgist.taoyao.signal.party.media.ClientWrapper.SubscribeType;
import com.acgist.taoyao.signal.party.media.Room;
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -39,7 +41,7 @@ import lombok.extern.slf4j.Slf4j;
} }
""" """
}, },
flow = "终端->服务-)终端" flow = "终端->信令服务-)终端"
) )
public class RoomEnterProtocol extends ProtocolRoomAdapter { public class RoomEnterProtocol extends ProtocolRoomAdapter {
@@ -59,7 +61,7 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter {
final Object rtpCapabilities = MapUtils.get(body, Constant.RTP_CAPABILITIES); final Object rtpCapabilities = MapUtils.get(body, Constant.RTP_CAPABILITIES);
final Object sctpCapabilities = MapUtils.get(body, Constant.SCTP_CAPABILITIES); final Object sctpCapabilities = MapUtils.get(body, Constant.SCTP_CAPABILITIES);
final String roomPassowrd = room.getPassword(); final String roomPassowrd = room.getPassword();
if(roomPassowrd != null && !roomPassowrd.equals(password)) { if(StringUtils.isNotEmpty(roomPassowrd) && !roomPassowrd.equals(password)) {
throw MessageCodeException.of(MessageCode.CODE_3401, "密码错误"); throw MessageCodeException.of(MessageCode.CODE_3401, "密码错误");
} }
// 进入房间 // 进入房间
@@ -74,7 +76,7 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter {
)); ));
room.broadcast(message); room.broadcast(message);
log.info("进入房间:{} - {}", clientId, room.getRoomId()); log.info("进入房间:{} - {}", clientId, room.getRoomId());
// 推送房间用户信息 // 推送房间用户信息TODO event
final Message roomClientListMessage = this.roomClientListProtocol.build(); final Message roomClientListMessage = this.roomClientListProtocol.build();
roomClientListMessage.setBody(room.clientStatus()); roomClientListMessage.setBody(room.clientStatus());
client.push(roomClientListMessage); client.push(roomClientListMessage);

View File

@@ -24,7 +24,29 @@ import lombok.Setter;
* @author acgist * @author acgist
*/ */
@Protocol @Protocol
@Description @Description(
body = """
{
"diskspace": [
{
"path": "存储路径",
"free": 存储空闲,
"total": 存储总量
},
...
],
"maxMemory": 最大能用内存,
"freeMemory": 空闲内存,
"totalMemory": 已用内存,
"osArch": "系统架构",
"osName": "系统名称",
"osVersion": "系统版本",
"javaVmName": "虚拟机名称",
"javaVersion": "虚拟机版本",
"cpuProcessors": CPU核心数量
}
"""
)
public class SystemInfoProtocol extends ProtocolClientAdapter { public class SystemInfoProtocol extends ProtocolClientAdapter {
public static final String SIGNAL = "system::info"; public static final String SIGNAL = "system::info";
@@ -38,10 +60,10 @@ public class SystemInfoProtocol extends ProtocolClientAdapter {
final Map<String, Object> info = new HashMap<>(); final Map<String, Object> info = new HashMap<>();
// 硬盘 // 硬盘
final List<Diskspace> diskspace = new ArrayList<>(); final List<Diskspace> diskspace = new ArrayList<>();
// File.listRoots(); -> 不全 // File.listRoots();
// FileSystems.getDefault().getFileStores(); -> 重复 // FileSystems.getDefault().getFileStores();
Stream.of(File.listRoots()).forEach(v -> { Stream.of(File.listRoots()).forEach(v -> {
diskspace.add(new Diskspace(v.getPath(), v.getTotalSpace(), v.getFreeSpace())); diskspace.add(new Diskspace(v.getPath(), v.getFreeSpace(), v.getTotalSpace()));
}); });
info.put("diskspace", diskspace); info.put("diskspace", diskspace);
// 内存 // 内存
@@ -53,8 +75,8 @@ public class SystemInfoProtocol extends ProtocolClientAdapter {
info.put("freeMemoryGracefully", FileUtils.formatSize(runtime.freeMemory())); info.put("freeMemoryGracefully", FileUtils.formatSize(runtime.freeMemory()));
info.put("totalMemoryGracefully", FileUtils.formatSize(runtime.totalMemory())); info.put("totalMemoryGracefully", FileUtils.formatSize(runtime.totalMemory()));
// 其他 // 其他
info.put("osName", System.getProperty("os.name"));
info.put("osArch", System.getProperty("os.arch")); info.put("osArch", System.getProperty("os.arch"));
info.put("osName", System.getProperty("os.name"));
info.put("osVersion", System.getProperty("os.version")); info.put("osVersion", System.getProperty("os.version"));
info.put("javaVmName", System.getProperty("java.vm.name")); info.put("javaVmName", System.getProperty("java.vm.name"));
info.put("javaVersion", System.getProperty("java.version")); info.put("javaVersion", System.getProperty("java.version"));
@@ -71,10 +93,6 @@ public class SystemInfoProtocol extends ProtocolClientAdapter {
* 路径 * 路径
*/ */
private final String path; private final String path;
/**
* 总量
*/
private final Long total;
/** /**
* 空闲 * 空闲
*/ */
@@ -82,18 +100,22 @@ public class SystemInfoProtocol extends ProtocolClientAdapter {
/** /**
* 总量 * 总量
*/ */
private final String totalGracefully; private final Long total;
/** /**
* 空闲 * 空闲
*/ */
private final String freeGracefully; private final String freeGracefully;
/**
* 总量
*/
private final String totalGracefully;
public Diskspace(String path, Long total, Long free) { public Diskspace(String path, Long free, Long total) {
this.path = path; this.path = path;
this.total = total;
this.free = free; this.free = free;
this.totalGracefully = FileUtils.formatSize(total); this.total = total;
this.freeGracefully = FileUtils.formatSize(free); this.freeGracefully = FileUtils.formatSize(free);
this.totalGracefully = FileUtils.formatSize(total);
} }
} }