Enhance pipeline execution and voice preview handling for graceful call termination
- Introduce mechanisms in the pipeline to ensure that the end call process waits for the completion of the end speech before hanging up, improving user experience during call termination. - Update the useVoicePreview hook to handle server-initiated call endings gracefully, distinguishing between normal and error disconnections. - Adjust TTS stop frame timeout settings to optimize the timing of call terminations, ensuring timely responses without unnecessary delays. - Refactor related components to support the new end call logic, enhancing overall workflow management and user interaction.
This commit is contained in:
@@ -105,6 +105,8 @@ export function useVoicePreview(
|
||||
const localStreamRef = useRef<MediaStream | null>(null);
|
||||
const startingRef = useRef(false);
|
||||
const messageSeqRef = useRef(0);
|
||||
// 后端主动结束(工作流走到结束节点)标记:据此把随后的断开当作正常结束而非报错
|
||||
const endedByServerRef = useRef(false);
|
||||
// 工作流激活节点回调存进 ref,避免把它挂进 connect 依赖反复重建连接
|
||||
const onNodeActiveRef = useRef(onNodeActive);
|
||||
useEffect(() => {
|
||||
@@ -201,6 +203,19 @@ export function useVoicePreview(
|
||||
[releaseResources],
|
||||
);
|
||||
|
||||
// 连接断开时:若是后端主动收尾(call-ended),按正常结束处理(不报错);
|
||||
// 否则按异常失败处理。
|
||||
const closeOnRemoteEnd = useCallback(
|
||||
(message: string) => {
|
||||
if (endedByServerRef.current) {
|
||||
disconnect();
|
||||
} else {
|
||||
fail(message);
|
||||
}
|
||||
},
|
||||
[disconnect, fail],
|
||||
);
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
if (startingRef.current || pcRef.current || wsRef.current) return;
|
||||
if (!assistantId) {
|
||||
@@ -212,6 +227,7 @@ export function useVoicePreview(
|
||||
setError(null);
|
||||
setMicWarning(null);
|
||||
setMessages([]); // 新会话清空上一轮聊天记录
|
||||
endedByServerRef.current = false;
|
||||
setStatus("connecting");
|
||||
|
||||
// 麦克风是可选的:获取失败时继续建立仅接收后端音频的 WebRTC 会话。
|
||||
@@ -282,7 +298,7 @@ export function useVoicePreview(
|
||||
if (wsRef.current === ws) fail("语音信令连接失败。");
|
||||
};
|
||||
ws.onclose = () => {
|
||||
if (wsRef.current === ws) fail("语音信令连接已断开。");
|
||||
if (wsRef.current === ws) closeOnRemoteEnd("语音信令连接已断开。");
|
||||
};
|
||||
|
||||
// 2) 建 PeerConnection(纯 STUN,本机/局域网够用)
|
||||
@@ -392,6 +408,10 @@ export function useVoicePreview(
|
||||
) {
|
||||
// 工作流:后端报告当前激活节点,交给画布高亮
|
||||
onNodeActiveRef.current?.(msg.nodeId);
|
||||
} else if (msg?.type === "call-ended") {
|
||||
// 后端走到结束节点、正常收尾:随后的断开按正常结束处理,不报错
|
||||
endedByServerRef.current = true;
|
||||
onNodeActiveRef.current?.(null);
|
||||
}
|
||||
} catch {
|
||||
/* 非 JSON / 未知消息,忽略 */
|
||||
@@ -412,15 +432,18 @@ export function useVoicePreview(
|
||||
if (pcRef.current !== pc) return;
|
||||
if (pc.connectionState === "connected") setStatus("connected");
|
||||
else if (pc.connectionState === "failed")
|
||||
fail("WebRTC 音频连接失败。");
|
||||
closeOnRemoteEnd("WebRTC 音频连接失败。");
|
||||
else if (pc.connectionState === "closed")
|
||||
closeOnRemoteEnd("WebRTC 音频连接已断开。");
|
||||
};
|
||||
|
||||
pc.oniceconnectionstatechange = () => {
|
||||
if (pcRef.current !== pc) return;
|
||||
const st = pc.iceConnectionState;
|
||||
if (st === "connected" || st === "completed") setStatus("connected");
|
||||
else if (st === "failed") fail("WebRTC 音频连接失败。");
|
||||
else if (st === "disconnected") fail("WebRTC 音频连接已断开。");
|
||||
else if (st === "failed") closeOnRemoteEnd("WebRTC 音频连接失败。");
|
||||
else if (st === "disconnected")
|
||||
closeOnRemoteEnd("WebRTC 音频连接已断开。");
|
||||
};
|
||||
|
||||
// 3) 有麦克风时双向音频;否则明确声明只接收后端音频。
|
||||
@@ -453,7 +476,7 @@ export function useVoicePreview(
|
||||
} finally {
|
||||
startingRef.current = false;
|
||||
}
|
||||
}, [assistantId, fail, refreshDevices]);
|
||||
}, [assistantId, fail, closeOnRemoteEnd, refreshDevices]);
|
||||
|
||||
// 选择麦克风:更新选择;若会话正在发送麦克风音频,则用 WebRTC replaceTrack
|
||||
// 热切换轨道(无需重新协商),并把波形可视化重新接到新流。
|
||||
|
||||
Reference in New Issue
Block a user