# 消息定义 --- ## 概述 消息通道保持与业务的隔离性,包括鉴权等,独立于业务存在。 做为消息通道,理论上不应该与客户端用户登陆状态相关,应该只与应用关,即理论上一个应用应该在打开后即建立连接。用户登陆后应该在应用服务器中将此连接与用户进行关联而已,将要发送给此用户的消息发送到此通道中即可。当然,MQTT服务在建立连接,断开连接等事件结点均会发出WebHook消息。WebHook对应的被通知业务服务应该是HTTP服务。 MQTT消息结构体默认应该为MessagePack格式( It's like JSON. but fast and small.) ## MQTT WebHooks ### 配置WebHook链接地址 - 此服务地址不应该与客户端API一样对客户端开放,如要开放也请做好安全限制 - 此服务请求结构体支持MessagePack/Json格式(可配置,默认MessagePack),并在URL中配有签名字段(可选),约定密钥配置在MQTT服务器中 ## MQTT业务消息 消息结构体主要属性有两个:event, data,以激光笔为例 ```json { "event":"laser-pointer", "data":{ "start":[0.5,0.3], "end":[0.5,0.3], "color":[255,0,0], "thickness":3 } } ``` 消息接收者范围由MQTT的TOPIC来约束。 本文中默认topic根约定为: /zrsk/remote-assistant,下文中的所有TOPIC默认需要在头部添加`{{TOPIC-ROOT}}`来表达,如: - `/room/78Ue` 业务中完整TOIPC应为: `{{TOPIC-ROOT}}/room/78Ue`,即:`/zrsk/remote-assistant/room/78Ue` 如要发送给房间指定用户,消息体对应的Topic应该为: ` {{TOPIC-ROOT}}/room/{{ROOM_ID}}/user/{{UID}}, ` 如果是房间广播消息,那么消息体对应的Topic应该为: ` {{TOPIC-ROOT}}/room/{{ROOM_ID}}, ` 在进入房间后,客户端应该订阅如上两个对应的TOPIC即可实现对此房间此类消息的监听。 ## 客户端消息定义 ### 发起呼叫 用户创建会议,并呼叫会议参与方,一般每2秒发送一次消息,收到一次消息持续UI提示6秒,收到取消呼叫的消息显示呼叫被取消后,显示呼叫取消的UI停留2秒后隐藏。或在呼叫UI显示6秒后,中间没有新的呼叫消息到达,同样视为呼叫取消。 - topic: `{{TOPIC-ROOT}}/user/{{UID}}` ,UID为被呼叫的用户ID - event: `call` - data: - room_id: `string` 会议ID - title: `string` 发起呼叫的会议名称 - caller: `uid` 呼叫者ID,收到后应该从服务端获取此用户的用户信息,如getBriefUserInfo ### 呼叫取消 取消对指定用户参会的呼叫邀请 - topic: `{{TOPIC-ROOT}}/user/{{UID}}` ,UID为被呼叫的用户ID - event: `call-cancel` - data: - room_id: `string` 会议ID - title: `string` 发起呼叫的会议名称 - caller: `uid` 呼叫者ID,收到后应该从服务端获取此用户的用户信息,如getBriefUserInfo ### 激光笔 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}/user/{{UID}}` - event: `laser-pointer` - data: - start: `[2]float`, 点击按下时,当前点坐标相对屏幕坐标的百分比,结构为[x,y],如屏幕宽1920,当前点x是960,则这里应该传递0.5, - end: `[2]float`, 当点击抬起时,当前点坐标相对屏幕坐标的百分比,结构为[x,y],如屏幕宽1920,当前点x是960,则这里应该传递0.5, - color: `[3]uint8`, 结构顺序为RGB,即红,蓝,绿三原色,范围分别为0-255,如[255,0,0],即代表纯红色。 - thickness:`uint`, 连接start,与end之间的线段粗细,值单位是像素,一般为大于1的整数 ### 冻屏 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}/user/{{UID}}` - event: `screen-freeze` - data: - uid: `string`, 发起冻屏的用户ID,收到此消息后默认将此用户发布的视频流放到最大,此用户开始将之前用户的视频分享界面截图做为画板在上面进行绘画,过程视频流共享给当前会议内的所有人。 ### 广播消息 - topic: - 全局: `{{TOPIC-ROOT}}/system` , 客户端启动后建立MQTT连接后应该全程订阅 - 会议中: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}` ,客户端参与指定会议后,开始订阅,退出会议或会议结束后应该取消订阅 - 用户消息: `{{TOPIC-ROOT}}/user/{{UID}}`, 客户端启动后建立MQTT连接后,用户登陆后,应该使用当前用户ID来全程订阅,发布到此Topic下可能的消息类型有: - 管理员私信 - 聊天消息 - 会议呼叫 - 会议管理消息(禁麦,被踢出会议等) - 文件预览 - event: `boardcast` - data: - title: `string` 广播消息标题 - message: `string` 消息内容,本期暂时固定消息样式即可,后期考虑支持html富文本格式 - sender: - name: `string` 发送者名称 ### 发起对指定用户的文件预览 在会议中发起对指定用户的文件预览,首先需要将文件上传,后得到文件的相对路径,客户端收到消息后再将文件路径拼接到文件预览服务API中从而实现预览。 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}/user/{{UID}}` - event: `file_preview` - data: - name: `string` 文件名称 - path: `string` 文件路径 - mime_type: `string` 文件类型,如: `image/jpeg`、`application/pdf`等 ### 会议字幕 会议中所有用户的发言音频转字幕 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}` - event: `meeting-asr-subtitle` - data: - uid: `string` 用户ID - seg: `string` 用户当前ASR转换的结果 - status: `string`,可能的值为:正在讲话中:`steaming`,本句已经说完:`final` ### 会议结束 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}` - event: `end-meeting` - data: - reason: `string` __optional__ 可由系统生成详细的原因,如此字段存在,此信息应该以toast的方式在客户端进行提示。如:“XX用户挂断了”或“管理员结束了本次会议” - code: `int`, 0: 未知原因,1:用户挂断,2:管理员解散会议 ### 强制切换指定用户当前发布的视频源 - topic: `{{TOPIC-ROOT}}/room/{{ROOM_ID}}/user/{{UID}}` - event: `change-video-source` - data: - stream_id: `string` 可选的值由客户端在进入会议后上报本地可用的localStream设备列表 ## 服务端消息定义 ## 房间消息 - topic: `{{TOPIC-ROOT}}/system/room/{{ROOM_ID}}/event/{{EVENT}}` // 此Topic不允许用户客户端订阅 - ROOM_ID:`string` 同room.name - EVENT: `string` 同消息中的event - event: `room-event` - room_started: 会议开始 - room_finished: 会议结束 - participant_joined: 有人参会 - participant_left: 有人退出会议 - track_published: 有人发布流 - track_unpublished: 有人停止流发布 - data: - event: `string` 事件 - room: `Room` 结构体参考[Room](structs.md#room) - participant: `ParticipantInfo` 结构体参考[ParticipantInfo](structs.md#participantinfo) 当event与participant或track相关时可用 - track: `TrackInfo` 结构体参考[TrackInfo](structs.md#trackinfo) 当event为track_unpublished,或track_unpublished时可用 消息格式参考 ```json { "event": "room_started", "room": { "sid": "RM_g8qHzqsDLYbf", "name": "f37p-vltp", "empty_timeout": 300, "departure_timeout": 20, "creation_time": 1735203759, "turn_password": "UodxhWh76Wtf3h2V9XmfiyisKKxpik93BFuyqdvTzG4", "enabled_codecs": [ { "mime": "audio/opus" }, { "mime": "audio/red" }, { "mime": "video/VP8" }, { "mime": "video/H264" }, { "mime": "video/VP9" }, { "mime": "video/AV1" } ] }, "participant": { "sid": "PA_5cRs7GQixq7Q", "identity": "Kilroy001", "state": 2, "joined_at": 1735203759, "name": "Kilroy001", "version": 2, "permission": { "can_subscribe": true, "can_publish": true, "can_publish_data": true } }, "id": "EV_e5mj23v3QCST", "created_at": 1735203759 } ```