Twilio 可编程语音呼叫立即完成

Posted

技术标签:

【中文标题】Twilio 可编程语音呼叫立即完成【英文标题】:Twilio Programmable Voice call immediately completes 【发布时间】:2017-03-08 14:04:42 【问题描述】:

我正在使用带有 Swift 和 Python 的新 Twilio 可编程语音 SDK。我已经开始了各自的快速入门项目,并且大部分情况下都有效。我能够获得有效的访问令牌,能够成功创建呼叫,甚至能够接听呼叫。问题出在房子的呼叫方。

当我尝试通过 Swift SDK 拨打电话时,通话在另一端开始响铃之前就已断开。

我在 Twilio 文档中读到,如果您不处理 status_callback 事件,client.calls.create 函数将立即返回完成状态。我试图添加这个,但每当我这样做时,我都会收到一条错误消息,指出密钥 status_callback 不是 client.calls.create 函数的有效参数。此外,我在任何地方都找不到任何实际如何处理通话状态的示例。

我的问题是我在这里做错了什么?任何帮助将不胜感激。

这是我的 Python 代码

@app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():

  account_sid = os.environ.get("ACCOUNT_SID", ACCOUNT_SID)
  api_key = os.environ.get("API_KEY", API_KEY)
  api_key_secret = os.environ.get("API_KEY_SECRET", API_KEY_SECRET)

  from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
  to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]

  client = Client(api_key, api_key_secret, account_sid)
  call = client.calls.create(url='http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient', to=to_number, from_=from_number)

  # return str(call.sid)
  resp = twilio.twiml.Response()
  resp.say("Thank you for calling");
  return str(resp)

这是我的相关 ios 代码。请记住,这不是我的全部来源。我只提供了在这种情况下应该有的东西。我的完整来源确实包括处理注册表和邀请代表。我也没有包括显示/隐藏我的活动呼叫 UI 的源,因为这没有问题。这只是为了说明我是如何拨打电话和接听电话完成委托的。

class VoiceManager: NSObject, PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, AVAudioPlayerDelegate 

    //MARK: - Singleton

    static let sharedManager = VoiceManager()

    //MARK: - Private Constants

    private let baseURLString = [MY_WEBAPP_ENDPOINT]
    private let accessTokenEndpoint = "/accessToken"

    //MARK: - Private Variables

    private var deviceTokenString:String?

    private var callInvite: TVOCallInvite?
    private var call: TVOCall?
    private var status: VoiceStatus = .idle

    //MARK: - Getters

    private func fetchAccessToken() -> String? 

        guard let accessTokenURL = URL(string: baseURLString + accessTokenEndpoint) else 
            return nil
        

        return try? String.init(contentsOf: accessTokenURL, encoding: .utf8)
    

    func placeCall(withParameters params: VoiceParameters, completion: @escaping (_ success: Bool, _ error: VAError?) -> Void) 

        if (call != nil) 
            call?.disconnect()
            completion(false, .phoneCallInProgress)
            status = .callEnded
            hideActiveCallUI()

         else 

            guard let accessToken = fetchAccessToken() else 
                completion(false, .phoneAccessTokenFetchFailed)
                return
            

            guard let paramsDict = params.toDictionary() else 
                completion(false, .phoneAccessTokenFetchFailed)
                return
            

            playOutgoingRingtone(completion:  [weak self] in

                if let strongSelf = self 

                    strongSelf.call = VoiceClient.sharedInstance().call(accessToken, params: [:], delegate: strongSelf) //NOTE: The params here are not necessary as the phone numbers for now are hard coded on the server

                    if (strongSelf.call == nil) 
                        strongSelf.status = .callEnded
                        completion(false, .phoneCallFailed)
                        return

                     else 
                        strongSelf.status = .callConnecting
                        self?.showActiveCallUI(withParameters: params)
                        completion(true, nil)
                    
                
            )
        
    

    // MARK: TVOCallDelegate
    func callDidConnect(_ call: TVOCall) 

        NSLog("callDidConnect:")

        self.call = call
        status = .inCall

        routeAudioToSpeaker()
    

    func callDidDisconnect(_ call: TVOCall) 

        NSLog("callDidDisconnect:")

        playDisconnectSound()

        self.call = nil
        status = .callEnded

        hideActiveCallUI()
    

    func call(_ call: TVOCall, didFailWithError error: Error) 

        NSLog("call:didFailWithError: \(error)");

        self.call = nil
        status = .callEnded
        hideActiveCallUI()
    

【问题讨论】:

您的 TwiML 应用程序语音 URL 指向什么?上面的/outbound 路由? 是的,它是上面的 /outgoing 路由。我试图将其设置为 /incoming,但我不确定如何正确处理该事件。当我让它指向那里并拨打“拨号”命令时,它仍然会断开原始呼叫,但当接收者应答时,它会在原始设备上回拨给我。 【参考方案1】:

这里是 Twilio 开发者宣传员。

您的 Swift 代码表明您的电话号码现在在服务器上是硬编码的。正如 Robert 所说的那样,问题是当您从 Twilio 向您的 /outbound 端点获得回调时,您正在使用 REST API 来生成调用。

实际发生的情况是,当您在启动应用程序调用的设备上@9​​87654321@ 时。然后,Twilio 向您的 /outbound 端点发出 HTTP 请求,以查看如何处理该调用。因此,您需要回复 TwiML 而不是 generating a new call with the REST API,以告诉 Twilio 下一步如何处理呼叫。

在这种情况下,听起来您正试图将dial 直接连接到另一个number。为此,您应该尝试以下响应:

@app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
  from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
  to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]

  resp = twilio.twiml.Response()
  with resp.dial(callerId=from_number) as r:
    r.number(to_number)
  return str(resp)

如果有帮助,请告诉我。

【讨论】:

这就是票!好的,是的,我不明白这里要做什么,因为文档并没有真正说明当您接到出站电话时要做什么。所有快速入门项目所做的只是调用 say 命令。非常感谢这里的帮助!【参考方案2】:

注意:我还在您通过 Twilio 支持创建的工单中做出了回应。

请检查您的帐户调试器,了解您收到的所有错误通知。这是一个例子:

尝试从 https://voiceapp-twilio.herokuapp.com/outgoing https://voiceapp-twilio.herokuapp.com/outgoing 返回了 HTTP 状态码 500。

Python Web 服务器返回了一条错误消息,其中包括:

TypeError: create() 得到了一个意外的关键字参数 'status_events' // Werkzeug 调试器

这看起来像是 Python 传出()函数中的代码错误。最值得注意的是,您正在尝试使用 REST API 创建一个新调用,而实际上您应该返回 TwiML。您应该返回包含 Dial 动词的 TwiML 以创建传出呼叫支路。

【讨论】:

您好,感谢我们的回复。我已经在 Twilio 支持的工单中回复了。

以上是关于Twilio 可编程语音呼叫立即完成的主要内容,如果未能解决你的问题,请参考以下文章

Twilio Video - 连接静音的可编程语音呼叫?

Twilio - 如何通过在 NodeJs 中提供呼叫 SID 来检索语音呼叫记录媒体文件

使用可编程语音 SDK 检索 Twilio 中的所有录音

如何以编程方式扩展应用引擎?

如何使用Twilio留下语音邮件?

如何在呼叫任何人之前创建 Twilio 会议?