twilio / twilio-voice.js

Twilio's JavaScript Voice SDK
Other
50 stars 53 forks source link

device.connect gives ringing tone and immediately a disconnect tone, call does not connect. #78

Closed ricksonmenezes closed 2 years ago

ricksonmenezes commented 2 years ago

I have integrated (migrated to) the 2.1.0 VoiceSDK (JS) version of Twilio but I am unable to get a call to go through.

I have changed all the old Twilio.Device calls and my code looks like this. (the vm is because we are doing the calls via vuejs)

`var tokenId = "<%=token%>";

            vm.device = new Twilio.Device(tokenId);

            const deviceOptions = {debug: false};
            vm.device.updateOptions(deviceOptions)
            vm.device.register();

            vm.device.on('error', (twilioError, call) => {
                console.log('An error has occurred: ', + twilioError);
            });

            vm.device.on('registered', device => {
                console.log('    twilio registered........');
            });

            vm.device.on('registering', device => {
                console.log('    twilio registering...    ');

        });
            vm.device.on('tokenWillExpire', () => {
           //     const token = getNewTokenViaAjax(); call API to get new token
                console.log('twilio token expired')
            //device.updateToken(token);
        });
            vm.device.on('unregistered', () => {
            console.log("    twilio unregistered...    ");
        });

            vm.device.on('incoming', conn => {
            console.log("Incoming connection from " + conn.parameters.From);
            conn.accept()
        });

            vm.device.on('connect', connection => {
                console.log("connection opened " + connection);
            });

            vm.device.on('disconnect', connection => {
                console.log("connection disonnected " + connection);

            });`

I am able to see the "device registered" in the console logs to which I assume the token received from the server works fine and twilio registers the device for incoming calls. The twilio version on the server is twilio-java-sdk 3.3.9 jar using the Capability Token API

TwilioCapability capability = new TwilioCapability(HIFIConstants.TWILIO_ACCOUNT_SID, HIFIConstants.TWILIO_AUTH_TOKEN)
capability.allowClientIncoming(HIFIConstants.TWILIO_CLIENT_INCOMING);
 capability.allowClientOutgoing(applicationSid);
token = capability.generateToken();

The logs shown when it tries to connect are the following. It seems to me that the logs are API endpoint calls. I am copying the payload as that is where all the information is to determine what the issue is.

Below are the payloads that are always sent on the same enpoint below. The response to all these calls is always "OK".

https://eventgw.us1.twilio.com/v4/EndpointEvents

{"publisher":"twilio-js-sdk","group":"connection","name":"outgoing","timestamp":"2022-02-12T05:16:37.183Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","preflight":false},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"network-information","name":"network-change","timestamp":"2022-02-12T05:16:37.188Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","downlink":10,"effective_type":"4g","rtt":50},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"get-user-media","name":"succeeded","timestamp":"2022-02-12T05:16:37.807Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","data":{"audioConstraints":{"audio":true}}},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"signaling-state","name":"have-local-offer","timestamp":"2022-02-12T05:16:37.854Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"dtls-transport-state","name":"new","timestamp":"2022-02-12T05:16:37.859Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-gathering-state","name":"gathering","timestamp":"2022-02-12T05:16:37.865Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-candidate","name":"ice-candidate","timestamp":"2022-02-12T05:16:37.873Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","candidate_type":"host","deleted":false,"ip":"172.27.32.1","is_remote":false,"port":51986,"priority":2122260223,"protocol":"udp","related_address":null,"related_port":null,"tcp_type":null,"transport_id":"0"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-candidate","name":"ice-candidate","timestamp":"2022-02-12T05:16:37.878Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","candidate_type":"host","deleted":false,"ip":"192.168.1.2","is_remote":false,"network-cost":10,"port":51987,"priority":2122194687,"protocol":"udp","related_address":null,"related_port":null,"tcp_type":null,"transport_id":"0"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-candidate","name":"ice-candidate","timestamp":"2022-02-12T05:16:37.967Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","candidate_type":"host","deleted":false,"ip":"172.27.32.1","is_remote":false,"port":9,"priority":1518280447,"protocol":"tcp","related_address":null,"related_port":null,"tcp_type":"active","transport_id":"0"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-candidate","name":"ice-candidate","timestamp":"2022-02-12T05:16:37.971Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","candidate_type":"host","deleted":false,"ip":"192.168.1.2","is_remote":false,"network-cost":10,"port":9,"priority":1518214911,"protocol":"tcp","related_address":null,"related_port":null,"tcp_type":"active","transport_id":"0"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-gathering-state","name":"complete","timestamp":"2022-02-12T05:16:37.975Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"connection","name":"outgoing-ringing","timestamp":"2022-02-12T05:16:38.131Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","hasEarlyMedia":false},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-connection-state","name":"checking","timestamp":"2022-02-12T05:16:38.555Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"settings","name":"edge","timestamp":"2022-02-12T05:16:38.559Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","edge":"singapore"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"signaling-state","name":"stable","timestamp":"2022-02-12T05:16:38.561Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"connection","name":"accepted-by-remote","timestamp":"2022-02-12T05:16:38.568Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"settings","name":"codec","timestamp":"2022-02-12T05:16:38.573Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","codec_params":"","selected_codec":"PCMU/8000"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"pc-connection-state","name":"connecting","timestamp":"2022-02-12T05:16:38.608Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-candidate","name":"selected-ice-candidate-pair","timestamp":"2022-02-12T05:16:38.665Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE","local_candidate":{"candidate_type":"prflx","deleted":false,"ip":"223.229.188.54","is_remote":false,"network-cost":10,"port":51987,"priority":1853759231,"protocol":"udp","related_address":"192.168.1.2","related_port":51987,"tcp_type":null,"transport_id":""},"remote_candidate":{"candidate_type":"host","deleted":false,"ip":"3.1.77.190","is_remote":true,"port":15920,"priority":2130706431,"protocol":"udp","related_address":null,"related_port":null,"tcp_type":null,"transport_id":""}},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"dtls-transport-state","name":"connecting","timestamp":"2022-02-12T05:16:38.668Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"ice-connection-state","name":"connected","timestamp":"2022-02-12T05:16:38.671Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"dtls-transport-state","name":"connected","timestamp":"2022-02-12T05:16:38.832Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"pc-connection-state","name":"connected","timestamp":"2022-02-12T05:16:38.835Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"connection","name":"disconnected-by-remote","timestamp":"2022-02-12T05:16:39.552Z","level":"INFO","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

{"publisher":"twilio-js-sdk","group":"dtls-transport-state","name":"closed","timestamp":"2022-02-12T05:16:39.564Z","level":"DEBUG","payload_type":"application/json","private":false,"payload":{"aggressive_nomination":false,"browser_extension":false,"dscp":true,"ice_restart_enabled":true,"platform":"WebRTC","sdk_version":"2.1.0","call_sid":"CAf86f583f7562b1e0c38050597d478d57","temp_call_sid":"TJS52891adc-f5d7-4fad-804f-de67c42d0a1e","direction":"OUTGOING","region":"ASIAPAC_SINGAPORE"},"publisher_metadata":{}}

What I see in the last two logs is that it is disconnected by remote and then closes. I am not disconnecting the call myself.

Also the twilio signal URL

wss://chunderw-vpc-gll.twilio.com/signal

that is called by the library stays on PENDING on the chrome network inspect for a long time and then moves to status UPGRADE. It's status is always 101 (switching?) but it never becomes 200. Don't know if this issue is related to the call not being able to connect..

I have been reading up on the scp paragraph in the github page It talks of configuring SCP so that the signal URL is allowed to go through. Given the logs I have above that is able to register the device, I am wondering if I need to add this scp tag on the page. In fact, I did try it but it gave me other errors like "CSP tag cannot be used in meta please use header" and "Refused to load the script because it violates the following Content Security Policy directive". This is possible as I am not using the write syntax but just wanted to mention what I have tried.

I don't get any other logs in the browser except the endpoint logs. The logs on twilio account website don't show up for the number I call to which I suppose is because the call has not gone through. The number I call is a US number and I have tried calling the number adding country code 1 and +1 and both fail. They fail with ringing tone and disconnect tone immediately after.

My previous version works. And for this migration, I have followed this post.

Hence, If anyone can throw some light on what I am missing (during migration) or doing wrong, would appreciate the help

Code to reproduce the issue:

If possible, please include debug logs. You can set the log level for twilio-video.js by calling connect with the logLevel property, e.g.

connect(token, { logLevel: 'debug' })

Then you can copy and paste or save the logs from your browser's console.

-->

TODO

Software versions:

manjeshbhargav commented 2 years ago

Hi @ricksonmenezes ,

This repository is for the Video SDK. Let me move your issue to the Voice SDK.

Thanks,

Manjesh

ricksonmenezes commented 2 years ago

Thanks for moving it Manjesh. Any idea what am I doing wrong? I have seen another issue on twilio-android saying 'disconnected by remote' which also appears to me on web(JS). In android app forum, they keep talking about their Twiml app not giving the proper response. In my case, our account does not have a TwiML app, it still works on production(perhaps it uses SID, authtoken, secret etc and that's good enough?).

So while I have migrated the JS library to 2.1, need to know if there is something I am missing (say like TwiMl app is mandatory if you upgrade Js from twilio-client to twilio-voice SDK but it wasn't mentioned in the docs?) due to which I get 'disconnected-by-remote'.

mhuynh5757 commented 2 years ago

Hi @ricksonmenezes , is it possible for you to migrate to Access Tokens instead of Capability Tokens? The latter are deprecated and no longer officially supported. This may solve your issue. There is also a snippet in the README for this repo that goes over some settings if you are having issues with Content Security Policy.

gwing33 commented 2 years ago

@manjeshbhargav after seeing your comment here, I decided to upgrade our token. When I switch out the capability token for the access token, calling no longer works. Is there an upgrade guide for what needs to be done here? I wasn't able to find one on Twilio docs.

ricksonmenezes commented 2 years ago

Hi @ricksonmenezes , is it possible for you to migrate to Access Tokens instead of Capability Tokens? The latter are deprecated and no longer officially supported. This may solve your issue. There is also a snippet in the README for this repo that goes over some settings if you are having issues with Content Security Policy.

@mhuynh5757 Thank you for your response. We eventually managed to resolve it by using a sample java app voice-javascript-sdk-quickstart-java that had twilio.sdk twilio 8.1.1. that had a twilio quickstart JS file that calls twilio voice sdk 2.1.1 which is in a singleJS file.

So yes, the documentation on Twilio mentions the updated way of created a device by using a constructor but it did not include the ways to register all the device components that we found in the quickstart.js namely

After handling the new voice SDK 2.1.1 JS file from this code below, our twilio integration began to work.

$(function () {
  const speakerDevices = document.getElementById("speaker-devices");
  const ringtoneDevices = document.getElementById("ringtone-devices");
  const outputVolumeBar = document.getElementById("output-volume");
  const inputVolumeBar = document.getElementById("input-volume");
  const volumeIndicators = document.getElementById("volume-indicators");
  const callButton = document.getElementById("button-call");
  const outgoingCallHangupButton = document.getElementById("button-hangup-outgoing");
  const callControlsDiv = document.getElementById("call-controls");
  const audioSelectionDiv = document.getElementById("output-selection");
  const getAudioDevicesButton = document.getElementById("get-devices");
  const logDiv = document.getElementById("log");
  const incomingCallDiv = document.getElementById("incoming-call");
  const incomingCallHangupButton = document.getElementById(
    "button-hangup-incoming"
  );
  const incomingCallAcceptButton = document.getElementById(
    "button-accept-incoming"
  );
  const incomingCallRejectButton = document.getElementById(
    "button-reject-incoming"
  );
  const phoneNumberInput = document.getElementById("phone-number");
  const incomingPhoneNumberEl = document.getElementById("incoming-number");
  const startupButton = document.getElementById("startup-button");

  let device;
  let token;

  // Event Listeners

  callButton.onclick = (e) => {
    e.preventDefault();
    makeOutgoingCall();
  };
  getAudioDevicesButton.onclick = getAudioDevices;
  speakerDevices.addEventListener("change", updateOutputDevice);
  ringtoneDevices.addEventListener("change", updateRingtoneDevice);

  // SETUP STEP 1:
  // Browser client should be started after a user gesture
  // to avoid errors in the browser console re: AudioContext
  startupButton.addEventListener("click", startupClient);

  // SETUP STEP 2: Request an Access Token
  async function startupClient() {
    log("Requesting Access Token...");

    try {
      const data = await $.getJSON("/token");
      log("Got a token.");
      token = data.token;
      setClientNameUI(data.identity);
      intitializeDevice();
    } catch (err) {
      console.log(err);
      log("An error occurred. See your browser console for more information.");
    }
  }

  // SETUP STEP 3:
  // Instantiate a new Twilio.Device
  function intitializeDevice() {
    logDiv.classList.remove("hide");
    log("Initializing device");
    device = new Twilio.Device(token, {
      logLevel: 1, 
      // Set Opus as our preferred codec. Opus generally performs better, requiring less bandwidth and
      // providing better audio quality in restrained network conditions.
      codecPreferences: ["opus", "pcmu"]
    });

    addDeviceListeners(device);

    // Device must be registered in order to receive incoming calls
    device.register();
  }

  // SETUP STEP 4:
  // Listen for Twilio.Device states
  function addDeviceListeners(device) {
    device.on("registered", function () {
      log("Twilio.Device Ready to make and receive calls!");
      callControlsDiv.classList.remove("hide");
    });

    device.on("error", function (error) {
      log("Twilio.Device Error: " + error.message);
    });

    device.on("incoming", handleIncomingCall);

    device.audio.on("deviceChange", updateAllAudioDevices.bind(device));

    // Show audio selection UI if it is supported by the browser.
    if (device.audio.isOutputSelectionSupported) {
      audioSelectionDiv.classList.remove("hide");
    }
  }

  // MAKE AN OUTGOING CALL

  async function makeOutgoingCall() {
    var params = {
      // get the phone number to call from the DOM
      To: phoneNumberInput.value,
    };

    if (device) {
      log(`Attempting to call ${params.To} ...`);

      // Twilio.Device.connect() returns a Call object
      const call = await device.connect({ params });

      // add listeners to the Call
      // "accepted" means the call has finished connecting and the state is now "open"
      console.log("Response ::: "+ JSON.stringify(call));
      call.on("accept", updateUIAcceptedOutgoingCall);
      call.on("disconnect", updateUIDisconnectedOutgoingCall);
      call.on("cancel", updateUIDisconnectedOutgoingCall);

      outgoingCallHangupButton.onclick = () => {
        log("Hanging up ...");
        call.disconnect();
      };

    } else {
      log("Unable to make call.");
    }
  }

  function updateUIAcceptedOutgoingCall(call) {
    log("Call in progress ...");
    callButton.disabled = true;
    outgoingCallHangupButton.classList.remove("hide");
    volumeIndicators.classList.remove("hide");
    bindVolumeIndicators(call);
  }

  function updateUIDisconnectedOutgoingCall() {
    log("Call disconnected.");
    callButton.disabled = false;
    outgoingCallHangupButton.classList.add("hide");
    volumeIndicators.classList.add("hide");
  }

  // HANDLE INCOMING CALL

  function handleIncomingCall(call) {
    log(`Incoming call from ${call.parameters.From}`);

    //show incoming call div and incoming phone number
    incomingCallDiv.classList.remove("hide");
    incomingPhoneNumberEl.innerHTML = call.parameters.From;

    //add event listeners for Accept, Reject, and Hangup buttons
    incomingCallAcceptButton.onclick = () => {
      acceptIncomingCall(call);
    };

    incomingCallRejectButton.onclick = () => {
      rejectIncomingCall(call);
    };

    incomingCallHangupButton.onclick = () => {
      hangupIncomingCall(call);
    };

    // add event listener to call object
    call.on("cancel", handleDisconnectedIncomingCall);
    call.on("disconnect", handleDisconnectedIncomingCall);
    call.on("reject", handleDisconnectedIncomingCall);
  }

  // ACCEPT INCOMING CALL

  function acceptIncomingCall(call) {
    call.accept();

    //update UI
    log("Accepted incoming call.");
    incomingCallAcceptButton.classList.add("hide");
    incomingCallRejectButton.classList.add("hide");
    incomingCallHangupButton.classList.remove("hide");
  }

  // REJECT INCOMING CALL

  function rejectIncomingCall(call) {
    call.reject();
    log("Rejected incoming call");
    resetIncomingCallUI();
  }

  // HANG UP INCOMING CALL

  function hangupIncomingCall(call) {
    call.disconnect();
    log("Hanging up incoming call");
    resetIncomingCallUI();
  }

  // HANDLE CANCELLED INCOMING CALL

  function handleDisconnectedIncomingCall() {
    log("Incoming call ended.");
    resetIncomingCallUI();
  }

  // MISC USER INTERFACE

  // Activity log
  function log(message) {
    logDiv.innerHTML += `<p class="log-entry">&gt;&nbsp; ${message} </p>`;
    logDiv.scrollTop = logDiv.scrollHeight;
  }

  function setClientNameUI(clientName) {
    var div = document.getElementById("client-name");
    div.innerHTML = `Your client name: <strong>${clientName}</strong>`;
  }

  function resetIncomingCallUI() {
    incomingPhoneNumberEl.innerHTML = "";
    incomingCallAcceptButton.classList.remove("hide");
    incomingCallRejectButton.classList.remove("hide");
    incomingCallHangupButton.classList.add("hide");
    incomingCallDiv.classList.add("hide");
  }

  // AUDIO CONTROLS

  async function getAudioDevices() {
    await navigator.mediaDevices.getUserMedia({ audio: true });
    updateAllAudioDevices.bind(device);
  }

  function updateAllAudioDevices() {
    if (device) {
      updateDevices(speakerDevices, device.audio.speakerDevices.get());
      updateDevices(ringtoneDevices, device.audio.ringtoneDevices.get());
    }
  }

  function updateOutputDevice() {
    const selectedDevices = Array.from(speakerDevices.children)
      .filter((node) => node.selected)
      .map((node) => node.getAttribute("data-id"));

    device.audio.speakerDevices.set(selectedDevices);
  }

  function updateRingtoneDevice() {
    const selectedDevices = Array.from(ringtoneDevices.children)
      .filter((node) => node.selected)
      .map((node) => node.getAttribute("data-id"));

    device.audio.ringtoneDevices.set(selectedDevices);
  }

  function bindVolumeIndicators(call) {
    call.on("volume", function (inputVolume, outputVolume) {
      var inputColor = "red";
      if (inputVolume < 0.5) {
        inputColor = "green";
      } else if (inputVolume < 0.75) {
        inputColor = "yellow";
      }

      inputVolumeBar.style.width = Math.floor(inputVolume * 300) + "px";
      inputVolumeBar.style.background = inputColor;

      var outputColor = "red";
      if (outputVolume < 0.5) {
        outputColor = "green";
      } else if (outputVolume < 0.75) {
        outputColor = "yellow";
      }

      outputVolumeBar.style.width = Math.floor(outputVolume * 300) + "px";
      outputVolumeBar.style.background = outputColor;
    });
  }

  // Update the available ringtone and speaker devices
  function updateDevices(selectEl, selectedDevices) {
    selectEl.innerHTML = "";

    device.audio.availableOutputDevices.forEach(function (device, id) {
      var isActive = selectedDevices.size === 0 && id === "default";
      selectedDevices.forEach(function (device) {
        if (device.deviceId === id) {
          isActive = true;
        }
      });

      var option = document.createElement("option");
      option.label = device.label;
      option.setAttribute("data-id", id);
      if (isActive) {
        option.setAttribute("selected", "selected");
      }
      selectEl.appendChild(option);
    });
  }
});
mhuynh5757 commented 2 years ago

Thanks, we'll file a ticket to see if we can improve our docs, will close this ticket now.

Ken-vdE commented 2 years ago

I'm having the same issue. However, adding device.register() did not help, unfortunately... This is the entire log, creating a device and calling connect() to create an outgoing call (no errors in Twilio console, just immediate disconnect and no call is created): image

I'm using the new AccessToken system exactly like the quickstart example. Can anyone help me?

bld010 commented 2 years ago

@Ken-vdE Hi there! Developer Educator here ... Let's try to figure this out.

First thing I would check is the AccessToken. Decode it at jwt.io and make sure it matches what is listed on the AccessToken docs page.

Next, I would check in the Twilio Console's Call Logs and see if a call is being created. (This is different from the Twilio.Call instance that is returned within your client-side application) Do you see a new call? Or do you see a parent call and a child call? Or no calls at all?

If there are no calls in the call log, that tells me something is probably wrong with the AccessToken.

If you see one call (a parent call), but no child call, that tells me something may be wrong with your TwiML. In these cases, your application is able to connect to Twilio (so Twilio creates a new Call Resource that you can see in your Call Logs), but Twilio may not be receiving valid TwiML instructions in response to the HTTP request that it sends to your TwiML App's Voice Configuration Request URL. Check that your TwiML App's Voice URL is configured properly, and that the TwiML you expect is being returned from that endpoint (this is probably <Dial><Number> or <Dial><Client>). If you're passing a ConnectOptions argument in device.connect(), make sure that the parameter names you're sending over from your frontend match the parameter names you're expecting in your TwiML endpoint.

If this doesn't help, can you screenshot any errors you're getting in your Twilio Console? If you see a parent call in your Console's call logs, expand all of the information in there and send a screenshot.

Ken-vdE commented 2 years ago

@bld010 Hi Brianna, Thank you for your quick response. Unfortunately I've already tried your troubleshooting steps.

I've checked the used token. When I decode it, it looks exactly as it should. The access keys and grants are correct. Is there anything specific that you think I should triple check?

No call logs have been created, which seems logical because it does not look like my application actually creates a call because it is 'disconnected' immediately.

Also no error logs are added with regards to incorrect parameters being sent to my app through a webhook, because no webhook is fired (I don't think anything is sent to Twilio when I try to start the call).

The @twilio/voice-sdk plugin does make 3 POST requests to https://eventgw.us1.twilio.com/v4/EndpointEvents with these payloads: image image image

Does that help?

bld010 commented 2 years ago

@Ken-vdE Hmm... Are you able to show me how you're instantiating your device and how you're calling device.connect()?

Also, are you using Vue? Are you saving the device in the Vue component's data object? Are you passing the device as a prop?

I notice in your screenshot that there are things like "Device3" and "Call2" ... are you naming those or is that happening automatically?

Ken-vdE commented 2 years ago

@bld010 You pointed me in the right direction!

Yes, I was using Vue. I created a device and passed it to a Vuex store module. Then I retrieved it using a Vuex getter in another Vue component. Using the 'gotten' device, I tried to call .connect().

I refactored the code to create and return the device within a single file and removed any Vue bindings so Vue proxies etc. aren't able to mess with the device object. The only way the application can create and retrieve the device is through 'native' javascript:

import { Device } from '@twilio/voice-sdk';

/**
 * Do NOT bind this object in Vue. Vue wraps proxies around it and that causes issues.
 *
 * @type { import('@twilio/voice-sdk').Device }
 */
let device = null;

/**
 * @param {String} token
 * @returns {Device}
 */
export function createDevice(token) {
    return device = new Device(token, {
        // See https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice#deviceoptions
        logLevel: 0,
        // Set Opus as our preferred codec. Opus generally performs better, requiring less bandwidth and
        // providing better audio quality in restrained network conditions.
        codecPreferences: ['opus', 'pcmu'],
    });
}

/**
 * @returns { import('@twilio/voice-sdk').Device }
 */
export function getDevice() {
    return device;
}

See https://stackoverflow.com/a/73231748/3017716 for more information.

Thank your for your help!

bld010 commented 2 years ago

@Ken-vdE Happy to help!