2f5d35e
use serde::Deserialize;
/// LiveKit WebRTC 服务配置。`url` 下发给浏览器,`api_url` 供服务端签发
/// token / 调用 RoomService,两者在同一部署里通常指向同一进程。
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct LiveKitConfig {
pub enabled: bool,
/// 浏览器侧 WebSocket 地址(ws:// 或 wss://)。
pub url: String,
/// 服务端侧 HTTP 地址(http:// 或 https://)。
pub api_url: String,
pub api_key: String,
pub api_secret: String,
pub token_ttl_secs: u64,
pub empty_timeout_secs: u32,
pub departure_timeout_secs: u32,
pub default_max_participants: u32,
/// Redis 中参与者在场记录的存活时间;每次 webhook 事件续期。
pub presence_ttl_secs: u64,
pub hand_ttl_secs: u64,
/// 录制/推流能力开关;为 false 时相关接口直接拒绝。
pub recording_enabled: bool,
}
impl Default for LiveKitConfig {
fn default() -> Self {
Self {
enabled: false,
url: String::new(),
api_url: String::new(),
api_key: String::new(),
api_secret: String::new(),
token_ttl_secs: 7200,
empty_timeout_secs: 300,
departure_timeout_secs: 20,
default_max_participants: 50,
presence_ttl_secs: 43_200,
hand_ttl_secs: 3_600,
recording_enabled: false,
}
}
}
fn scheme_ok(value: &str, allowed: &[&str]) -> bool {
match value.split_once("://") {
Some((scheme, rest)) => {
!rest.is_empty() && allowed.contains(&scheme.to_ascii_lowercase().as_str())
}
None => false,
}
}
impl LiveKitConfig {
pub fn is_valid(&self) -> Result<(), String> {
if !self.enabled {
return Ok(());
}
if self.url.trim().is_empty() {
return Err("livekit.url must not be empty when livekit is enabled".to_string());
}
if self.api_url.trim().is_empty() {
return Err("livekit.api_url must not be empty when livekit is enabled".to_string());
}
if !scheme_ok(self.url.trim(), &["ws", "wss"]) {
return Err("livekit.url must be a ws:// or wss:// URL".to_string());
}
if !scheme_ok(self.api_url.trim(), &["http", "https"]) {
return Err("livekit.api_url must be an http:// or https:// URL".to_string());
}
if self.api_key.trim().is_empty() {
return Err("livekit.api_key must not be empty when livekit is enabled".to_string());
}
if self.api_secret.trim().is_empty() {
return Err("livekit.api_secret must not be empty when livekit is enabled".to_string());
}
if self.token_ttl_secs == 0
|| self.empty_timeout_secs == 0
|| self.departure_timeout_secs == 0
|| self.default_max_participants == 0
|| self.presence_ttl_secs == 0
|| self.hand_ttl_secs == 0
{
return Err("livekit numeric settings must be greater than zero".to_string());
}
Ok(())
}
/// 前端可安全暴露的字段(绝不包含 api_secret)。
pub fn browser_url(&self) -> &str {
self.url.trim()
}
}