feat: 添加双人对练语音通话功能
Some checks failed
continuous-integration/drone/push Build is failing

- 后端:扩展 SSE 支持 WebRTC 信令消息转发
- 前端:创建 WebRTC 连接管理模块 (webrtc.ts)
- 前端:创建 useVoiceCall 组合式函数
- 前端:在对练房间添加语音通话 UI
- 集成 Web Speech API 实现语音转文字
This commit is contained in:
yuliang_guo
2026-01-28 15:45:47 +08:00
parent c27ad55e95
commit c5d460b413
6 changed files with 1254 additions and 19 deletions

View File

@@ -48,6 +48,13 @@ class JoinRoomRequest(BaseModel):
class SendMessageRequest(BaseModel):
"""发送消息请求"""
content: str = Field(..., description="消息内容")
source: Optional[str] = Field("text", description="消息来源: text/voice")
class WebRTCSignalRequest(BaseModel):
"""WebRTC 信令请求"""
signal_type: str = Field(..., description="信令类型: voice_offer/voice_answer/ice_candidate/voice_start/voice_end")
payload: dict = Field(..., description="信令数据SDP/ICE候选等")
class RoomResponse(BaseModel):
@@ -399,6 +406,60 @@ async def send_message(
}
@router.post("/{room_code}/signal", summary="发送WebRTC信令")
async def send_signal(
room_code: str,
request: WebRTCSignalRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
发送 WebRTC 信令消息
信令类型:
- voice_start: 发起语音通话
- voice_offer: SDP Offer
- voice_answer: SDP Answer
- ice_candidate: ICE 候选
- voice_end: 结束语音通话
"""
service = PracticeRoomService(db)
# 获取房间
room = await service.get_room_by_code(room_code.upper())
if not room:
raise HTTPException(status_code=404, detail="房间不存在")
# 检查用户是否在房间中
user_role = room.get_user_role(current_user.id)
if not user_role:
raise HTTPException(status_code=403, detail="您不是房间参与者")
# 验证信令类型
valid_signal_types = ["voice_start", "voice_offer", "voice_answer", "ice_candidate", "voice_end"]
if request.signal_type not in valid_signal_types:
raise HTTPException(status_code=400, detail=f"无效的信令类型,必须是: {', '.join(valid_signal_types)}")
# 发送信令消息(作为系统消息存储,用于 SSE 推送)
message = await service.send_message(
room_id=room.id,
user_id=current_user.id,
content=None, # 信令消息不需要文本内容
role_name=None,
message_type=request.signal_type,
extra_data=request.payload
)
return {
"code": 200,
"message": "信令发送成功",
"data": {
"signal_type": request.signal_type,
"sequence": message.sequence
}
}
@router.get("/{room_code}/messages", summary="获取消息列表")
async def get_messages(
room_code: str,