如何停止Twilio WebSocket已发送的音频播放?(Python)
解决方案
一、修复停止媒体流的404错误
Twilio的Call Streams REST资源是只读的,无法通过update方法修改状态,所以你调用stop_media_stream时会返回404。正确的停止流方式是通过WebSocket发送<Stop>事件,而非REST API。
修改你的取消任务函数,添加WebSocket停止事件的发送:
async def cancel_tasks(call_sid, stream_sid, twilio_ws): global listen_task,send_to_tts,send_stream_task # 取消正在运行的任务 if call_sid in send_stream_task: if send_stream_task[call_sid].cancel(): print("\n\nsend stream cancelled correctly\n\n") if call_sid in listen_task: if listen_task[call_sid].cancel(): print("Stream Sid: ",stream_sid) print("\n\nlisten cancelled correctly\n\n") if call_sid in send_to_tts: if send_to_tts[call_sid].cancel(): print("\n\nsend to TTS cancelled correctly\n\n") # 通过WebSocket发送停止流事件 if twilio_ws and twilio_ws.open: stop_message = json.dumps({ 'event': 'stop', 'streamSid': stream_sid }) await twilio_ws.send_text(stop_message) print("\n\nStream stopped via WebSocket\n\n")
二、解决已发送音频持续播放的问题
Twilio会缓存已接收的媒体包,即使停止流,缓存的音频仍会播放。可以通过以下几种方式解决:
1. 控制音频块大小,减少缓存量
将Eleven Labs返回的音频流拆分为更小的块发送(比如每块200ms以内),这样取消任务时,Twilio缓存的音频量极少,播放残留时间可忽略。
修改你的stream函数,调整音频块的处理逻辑:
async def stream(audio_stream, twilio_ws, stream_sid, call_sid): global send_stream_task print(f"\n\nNew streamSID: {stream_sid}\n\n") # 定义每块音频的字节数(16kHz单声道PCM,每200ms对应6400字节) chunk_size = 6400 buffer = b"" async for chunk in audio_stream: if chunk: buffer += chunk # 当缓冲区达到指定大小,发送并清空 while len(buffer) >= chunk_size: audio_chunk = buffer[:chunk_size] buffer = buffer[chunk_size:] b64_audio = base64.b64encode(audio_chunk).decode('utf-8') message = json.dumps({ 'event': 'media', 'streamSid': stream_sid, 'media': {'payload': b64_audio} }) send_stream_task[call_sid] = asyncio.create_task(twilio_ws.send_text(message)) await send_stream_task[call_sid] # 发送剩余的缓冲区音频 if buffer: b64_audio = base64.b64encode(buffer).decode('utf-8') message = json.dumps({ 'event': 'media', 'streamSid': stream_sid, 'media': {'payload': b64_audio} }) await twilio_ws.send_text(message)
2. 发送静音音频覆盖缓存
取消任务并停止流后,立刻发送一段静音的媒体包,Twilio会优先播放静音,覆盖缓存中的剩余音频:
async def send_silence(twilio_ws, stream_sid, duration_ms=500): # 生成静音音频(16kHz单声道PCM,静音样本为0) sample_rate = 16000 samples = int(sample_rate * duration_ms / 1000) silence = b"\x00\x00" * samples b64_silence = base64.b64encode(silence).decode('utf-8') message = json.dumps({ 'event': 'media', 'streamSid': stream_sid, 'media': {'payload': b64_silence} }) await twilio_ws.send_text(message) # 在cancel_tasks函数末尾添加: await send_silence(twilio_ws, stream_sid)
3. 重新发起流连接
如果上述方法效果不佳,可以在停止当前流后,重新通过Twilio的XML指令发起新的流连接,旧的缓存会被清空:
# 定义重新发起流的接口 @application.post('/reconnect-stream') async def reconnect_stream(call_sid: str = Form(...)): response = VoiceResponse() connect = Connect() URL = f"wss://{PUBLIC_URL}/stream" STATUS_URL = f"https://{PUBLIC_URL}/status-call" connect.stream(url=URL, status_callback=STATUS_URL, status_callback_method='POST', name=str("<name>")) response.append(connect) return Response(content=str(response), media_type='text/xml') # 在cancel_tasks后,调用这个接口更新通话的XML async def restart_call_stream(call_sid): account_sid = os.environ['TWILIO_ACCOUNT_SID'] auth_token = os.environ['TWILIO_AUTH_TOKEN'] client = Client(account_sid, auth_token) client.calls(call_sid).update( url=f"https://{PUBLIC_URL}/reconnect-stream?call_sid={call_sid}", method="POST" )
三、总结
优先采用控制音频块大小的方式,从根源减少缓存量;配合WebSocket发送停止事件和静音包,可快速终止音频播放。如果需要彻底清除缓存,可选择重新发起流连接。
内容的提问来源于stack exchange,提问作者JPSL
相关产品推荐
相关产品推荐

