如何同步Twilio通话流程与recordingStatusCallback的录音处理?
解决Twilio通话流程中录音处理与通话同步的问题
问题概述
需要实现的通话流程:
- 发起通话
- 分析录制的用户输入
- 根据用户输入生成播放提示语
- 向用户播放响应内容
当前遇到的问题:使用recordingStatusCallback处理录音时,通话流程会在录音下载完成前继续执行,导致无法将录音处理结果用于后续通话环节。需要调整流程,确保通话等待录音处理完成后再继续。
现有代码
from fastapi import FastAPI, Request, Response from twilio.twiml.voice_response import VoiceResponse from twilio.rest import Client import urllib.parse from urllib.parse import urljoin import time import logging logger = logging.getLogger(__name__) # 假设的全局会话存储和配置 session_store = {} TWILIO_ACCOUNT_SID = "your_account_sid" TWILIO_AUTH_CODE = "your_auth_code" TWILIO_PHONE_NUMBER = "your_twilio_number" def GetTextReplyToAudioQuery(recording_url): # 模拟录音处理逻辑 return "根据你的输入生成的响应" @app.get("/make_call") async def make_call(request: Request, to_number: str, txt_to_play: str): try: base_url = f"{request.url.scheme}://{request.url.netloc}" relative_url = "/start_call" encoded_txt = urllib.parse.quote(txt_to_play) relative_url += "?txt_to_play=" + encoded_txt complete_url = urljoin(base_url, relative_url) logger.info(complete_url) logger.info(f"Account SID : {TWILIO_ACCOUNT_SID}") logger.info(f"Auth Code : {TWILIO_AUTH_CODE}") client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_CODE) if 'Next_Prompt_To_Play' in session_store: del session_store['Next_Prompt_To_Play'] call = client.calls.create( to=to_number, from_=TWILIO_PHONE_NUMBER, url=complete_url ) return {"message": f"Call initiated to {to_number}", "call_sid": call.sid} except Exception as e: return {"error": f"error in call initiation : {e}"} @app.post("/start_call") async def start_call(request: Request): try: form = await request.form() txt_to_play = request.query_params.get('txt_to_play') response = VoiceResponse() response.say(txt_to_play) response.record( max_length=30, action="/handle_call", recordingStatusCallback="/handle_recording", recordingStatusCallbackEvent="completed", method="POST" ) return Response(content=str(response), media_type="application/xml") except Exception as e: return {"error": f"error in starting the call: {e}"} event_occurred = False @app.post("/handle_recording") async def handle_recording(request: Request): try: form = await request.form() recording_url = form['RecordingUrl'] recording_duration = form['RecordingDuration'] print(f"Recording URL: {recording_url}") print(f"Recording Duration: {recording_duration} seconds") query_response = GetTextReplyToAudioQuery(recording_url) session_store['Next_Prompt_To_Play'] = [query_response] global event_occurred if 'Next_Prompt_To_Play' in session_store and session_store['Next_Prompt_To_Play']: event_occurred = True print("Event fired") return Response(content=str(VoiceResponse()), media_type="application/xml") except Exception as e: return {"error": f"error in call recording: {e}"} @app.post("/handle_call") async def handle_callflow(request: Request): try: response = VoiceResponse() response.redirect("/handle_call_further") return Response(content=str(response), media_type="application/xml") except Exception as e: return {"error": f"error in call recording: {e}"} @app.post("/handle_call_further") async def handle_call_further_function(request: Request): try: txt_to_play = "Lets try this." global event_occurred wait_cnt = 5 cnt = 0 while cnt < wait_cnt: if event_occurred: if 'Next_Prompt_To_Play' in session_store and session_store['Next_Prompt_To_Play']: txt_to_play = session_store['Next_Prompt_To_Play'][0] event_occurred = False print("Handled the event") break cnt += 1 print(f"Waiting for the text : {cnt}") time.sleep(1) response = VoiceResponse() response.say(txt_to_play) return Response(content=str(response), media_type="application/xml") except Exception as e: return {"error": f"error in call recording: {e}"}
解决方案
核心思路是移除异步的recordingStatusCallback,将录音处理逻辑直接放到<Record>组件的action端点中。Twilio在用户停止录音后会立即调用action指定的接口,此时录音URL已经可用,无需异步等待,从而保证流程顺序同步。
修改后的关键代码
- 调整
/start_call端点,移除recordingStatusCallback相关配置,将action指向新的处理端点:
@app.post("/start_call") async def start_call(request: Request): try: form = await request.form() txt_to_play = request.query_params.get('txt_to_play') response = VoiceResponse() response.say(txt_to_play) # 直接将录音完成后的处理逻辑放到action端点 response.record( max_length=30, action="/handle_recording_and_respond", # 修改为新的处理端点 method="POST" ) return Response(content=str(response), media_type="application/xml") except Exception as e: return {"error": f"error in starting the call: {e}"}
- 新增
/handle_recording_and_respond端点,在该端点中完成录音处理并生成响应:
@app.post("/handle_recording_and_respond") async def handle_recording_and_respond(request: Request): try: form = await request.form() recording_url = form['RecordingUrl'] recording_duration = form['RecordingDuration'] # 处理录音,生成响应内容 query_response = GetTextReplyToAudioQuery(recording_url) # 直接向用户播放生成的响应 response = VoiceResponse() response.say(query_response) return Response(content=str(response), media_type="application/xml") except Exception as e: return {"error": f"error in processing recording and responding: {e}"}
- 删除原有的
/handle_recording、/handle_call、/handle_call_further端点及全局变量event_occurred,简化流程。
流程说明
修改后的执行顺序完全符合需求:
make_call:发起通话start_call:播放初始提示语并开始录音- 用户停止录音后,Twilio立即调用
/handle_recording_and_respond - 在该端点中处理录音、生成响应提示语
- 向用户播放响应内容
这种方案避免了异步回调的顺序问题,同时去掉了不可靠的全局变量轮询,提升了代码的可靠性和性能。
内容的提问来源于stack exchange,提问作者Preeti v
相关产品推荐
相关产品推荐

