2
votes

I am using the new Twilio Programmable Voice SDK with Swift and Python. I have begun with the respective quick starter projects, and for the most part things work. I'm able to get a valid access token, I'm able to successfully create a call, and I am even able to receive the call. The problem is on the caller side of the house.

When I try to make a call via the Swift SDK the call is disconnected before it ever starts to ring on the other end.

I have read in the Twilio docs that the client.calls.create function will immediately return a status of complete if you do not handle the status_callback event. I have tried to add this but whenever I do I get an error saying the the key status_callback is not a valid parameter for the client.calls.create function. Also, I cannot find any examples anywhere of actually how to handle the call status.

My question is what in the wold am I doing wrong here? Any help would be greatly appreciated.

Here is my Python code

@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)

Here is my relevant iOS code. Please bear in mind that this is NOT my full source. I have only provided what should be necessary in this event. My full source does include handling the registry and invite delegates. I also did not include my source that shows/hides my active call UI as there are no problems with that. This is simply to show how I am placing a call and receiving the call complete delegate.

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()
    }
}
2
What does your TwiML Application voice URL point to? The /outbound route above?philnash
Yes it is the /outgoing route above. I have tried to set it to /incoming, but I'm not sure how to handle that event correct. When I had it pointing there and called the "dial" command it would still disconnect the original call, but when the recipient answered it would call me back on the original device.miken.mkndev

2 Answers

2
votes

Twilio developer evangelist here.

Your Swift code says that your phone numbers are hard coded on the server right now. The issue, as Robert has said too, is that you are using the REST API to generate a call when you get a callback from Twilio to your /outbound endpoint.

What is actually happening is that when you generate the call in Swift on the device that starts the call for the application. Twilio then makes an HTTP request to your /outbound endpoint to see what to do with that call. So, instead of generating a new call with the REST API, you need to respond with TwiML to tell Twilio what to do with the call next.

In this case, it sounds like you are trying to dial straight onto another number. For that, you should try the following response:

@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)

Let me know if that helps.

1
votes

Note: I have also responded in the ticket you created with Twilio Support.

Please check your account debugger for the whole pile of error notifications you are getting. Here's an example:

An attempt to retrieve content from https://voiceapp-twilio.herokuapp.com/outgoing https://voiceapp-twilio.herokuapp.com/outgoing returned the HTTP status code 500.

The Python web server returned an error message, which includes this:

TypeError: create() got an unexpected keyword argument 'status_events' // Werkzeug Debugger

It looks like a code error in your Python outgoing() function. Most notably, you are attempting to use the REST API to create a new call, when you should actually be returning TwiML. You should be returning TwiML that includes the Dial verb to create the outgoing call leg.