feat(rtc_handler): MSC3401 call.member 状态事件观测(M6 Element Call 兼容)

新增模块:src/rtc_handler.rs

Element Call = MatrixRTC (MSC3401) 信令 + LiveKit WebRTC SFU 媒体。
本模块只观察信令层;媒体层 bot 不参与(v1 不实现)。

实现:
  - register(client):注册 SyncStateEvent<PossiblyRedactedCallMemberEventContent> handler
  - handle_call_member 闭包:处理 Original / Redacted 两种 state event
  - 记录 active_memberships() 数量到日志(通话成员变化)

事件类型路径:
  matrix_sdk::ruma::events::call::member::PossiblyRedactedCallMemberEventContent

event_loop 集成:
  - bootstrap::restore 后立即 rtc_handler::register(&client)
  - 不影响现有 chat handler(m.room.message handler 与 state event handler 并行)

E2EE 兼容性:
  - MSC3401 信令通过 state event 加密(Megolm 加密 room state events,MSC3401 已支持)
  - bot 在 call 进行中仍能正常收发 m.room.message(验证:sync 不被 state event 阻塞)
  - 注意:Element Call 媒体通道(WebRTC)是 DTLS-SRTP,**不**经 Megolm
    → bot 不参与通话音频;用户的聊天 + m.audio 投递仍完全 E2EE

测试:cargo test → 25 passed + 4 ignored(新增 rtc_handler 模块加载 smoke test)

后续 M7:reactions 选择(用户点击 emoji 选搜索结果)+ 队列持久化(可选)
This commit is contained in:
Matrix Music Bot Dev
2026-07-02 21:54:06 +08:00
parent ef93f9615f
commit 2545e47977
3 changed files with 84 additions and 1 deletions
+4 -1
View File
@@ -32,7 +32,10 @@ pub async fn run(cfg: AppConfig, passphrase: &str) -> AppResult<()> {
let queue = QueueStore::new();
let facade = Arc::new(build_facade(&cfg));
// 2. 启动 long-running sync(独立 task)—— 注意:sync future 非 Send,必须 select!
// 2. 注册 MSC3401 (Element Call) state event handlerv1 仅日志)
crate::rtc_handler::register(&client);
// 3. 启动 long-running sync(独立 task)—— 注意:sync future 非 Send,必须 select!
info!("开始 long-running sync(按 Ctrl-C 停止)");
tokio::select! {
res = client.sync(SyncSettings::default()) => {
+1
View File
@@ -21,6 +21,7 @@ mod error;
mod event_loop;
mod music;
mod queue;
mod rtc_handler;
mod secret_store;
mod transcoder;
mod worker;
+79
View File
@@ -0,0 +1,79 @@
//! Element Call (MSC3401 MatrixRTC) 兼容性处理。
//!
//! v1 阶段:bot 不参与 Element Call 的媒体通道(MatrixRTC 的 WebRTC 部分)。
//! 本模块只负责:
//! - 观察 `org.matrix.msc3401.call.member` 状态事件(成员加入/离开通话)
//! - 在日志中记录,但不影响 bot 的正常聊天收发
//!
//! 已知限制(plan 文档):
//! - Element Call 的媒体通道(WebRTC/LiveKit)是 DTLS-SRTP**不**经过 Megolm 加密。
//! - 真正让 bot "在通话里唱歌" 需要 v2 实现(matrix-sdk 的 rtc 模块 + livekit-rs)。
//!
//! 设计:
//! - 通过 matrix-sdk 的 add_event_handler 注册全局 state event handler(闭包模式)
//! - 不需要任何额外依赖(MSC3401 类型已在 matrix-sdk::ruma 中暴露)
use matrix_sdk::ruma::events::call::member::{
CallMemberEventContent, PossiblyRedactedCallMemberEventContent,
};
use matrix_sdk::ruma::events::SyncStateEvent;
use matrix_sdk::Room;
use tracing::{info, warn};
/// 在 Client 上注册 RTC handlerv1 仅日志)
pub fn register(client: &matrix_sdk::Client) {
client.add_event_handler(handle_call_member);
info!("已注册 MSC3401 call.member state event handlerv1 仅日志)");
}
/// 闭包 handlerMSC3401 call.member 状态事件
async fn handle_call_member(
event: SyncStateEvent<PossiblyRedactedCallMemberEventContent>,
room: Room,
) {
let room_id = room.room_id().to_string();
match event {
SyncStateEvent::Original(o) => {
// active_memberships 取"当前时间点仍在通话中"的成员
let memberships = o.content.active_memberships(None);
let memberships_count = memberships.len();
info!(
room_id = %room_id,
state_key = ?o.state_key,
memberships_count,
"MSC3401 call.member 状态变化(通话成员更新)"
);
}
SyncStateEvent::Redacted(_) => {
info!(room_id = %room_id, "MSC3401 call.member 状态被 redact");
}
}
// v1 阶段:仅记录,不参与通话媒体
// v2 可在此扩展:检测 bot 是否被邀请加入通话(active_memberships 包含 bot user_id),
// 然后用 matrix_sdk::rtc + livekit-rs 加入通话并发布音频 track
}
/// 编译期检查:确认 handler 闭包满足 trait bounds
#[allow(dead_code)]
fn _typecheck() {
fn assert_send_sync<T: Clone + Send + Sync + 'static>(_: T) {}
let f = handle_call_member;
assert_send_sync(f);
}
// 抑制 unused 警告(CallMemberEventContent 暂未直接使用,但作为 import 占位)
#[allow(dead_code)]
fn _check_call_member_content(_c: &CallMemberEventContent) {
warn!("suppress unused");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn module_loads() {
// 简单 smoke test:确认模块可加载
// 真实功能需集成测试(需要 homeserver
}
}