This is the Changelog for v1 of the Voice JavaScript SDK. Are you using v2 of the Voice JS SDK? Go to the v2 Changelog.
Updated November 1, 2023
We have identified an issue on Chromium-based browsers running on MacOS 14 (Sonoma) where the audio deteriorates during a call. This issue happens due to the excessive calls to MediaDevices: enumerateDevices() API. With this release, the SDK calls this API only when necessary to avoid audio deterioration.
WebRTC API Overrides (Beta)
The SDK now allows you to override WebRTC APIs using the following options and events. If your environment supports WebRTC redirection, such as Citrix HDX's WebRTC redirection technologies, your application can use this new beta feature for improved audio quality in those environments.
Device.Options.enumerateDevices
- Overrides the native MediaDevices.enumerateDevices
API.Device.Options.getUserMedia
- Overrides the native MediaDevices.getUserMedia
API.Device.Options.RTCPeerConnection
- Overrides the native RTCPeerConnection
class.connection.on('audio', handler(remoteAudio))
- Emitted after the HTMLAudioElement
for the remote audio is created.Device.audio.speakerDevices
and Device.audio.setInputDevice
will not properly switch speaker and microphone devices. You need to recreate the Device
object or refresh the page when the default microphone or speaker device is changed.Allow Connection specific ICE Servers
Developers can now opt to override rtcConfiguration
set within Device.options
per specific outgoing and incoming Connection
s.
To use this feature, a new parameter rtcConfiguration
can be passed to Device.connect
and Connection.accept
. The function signatures are now described as below.
1Device.connect(params?: Record<string, string>,2audioConstraints?: MediaTrackConstraints | boolean,3rtcConfiguration?: RTCConfiguration);45Connection.accept(audioConstraints?: MediaTrackConstraints | boolean,6rtcConfiguration?: RTCConfiguration);
Passing the rtcConfiguration
parameter to these functions will override any previously set rtcConfiguration
within Device.options
but not affect any other members set within Device.options
.
{ edge: 'sydney-ix' }
or { edge: tokyo-ix' }
in Device.setup()
or new Device()
options.1.13.0-beta2 has been promoted to 1.13.0 GA. Here's a summary of what's new in 1.13.0.
Voice diagnostics using runPreflight API
The SDK now supports a preflight test API which can help determine Voice calling readiness. The API creates a test call and will provide information to help troubleshoot call related issues. Please see the following for more details.
Connection warning data
Connection.on('warning') now provides data associated with the warning. This data can provide more details about the warning such as thresholds and WebRTC samples collected that caused the warning. The example below is a warning for high jitter. Please see Voice Insights SDK Events Reference for a list of possible warnings.
1connection.on('warning', (warningName, warningData) => {2console.log({ warningName, warningData });3});
Example output:
1{2"warningName": "high-jitter",3"warningData": {4"name": "jitter",56/**7* Array of jitter values in the past 5 samples that triggered the warning8*/9"values": [35, 44, 31, 32, 32],1011/**12* Array of samples collected that triggered the warning.13* See sample object format here https://www.twilio.com/docs/voice/sdks/javascript/v1/connection#sample14*/15"samples": [...],1617/**18* The threshold configuration.19* In this example, high-jitter warning will be raised if the value exceeded more than 3020*/21"threshold": {22"name": "max",23"value": 3024}25}26}
Added high-packets-lost-fraction
network warning. This new warning is raised when the average of the most recent seven seconds of packet-loss samples is greater than 3%
. When the average packet-loss over the most recent seven seconds is less than or equal to 1%
, then the warning is cleared.
The behavior for raising the constant-audio-level
warning has been updated. Now, the most recent ten seconds of volume values are recorded and then analyzed. If the standard deviation of these samples is less than 1% of the maximum audio value, then the warning is raised. When the standard deviation is greater than 1% and the warning has already been raised, then the warning is cleared.
We now log an outgoing
event to Insights when making an outbound call. This event also contains information whether the call is a preflight or not.
Added a Boolean field to the signaling payload for calls initiated by Device.runPreflight
for debugging purposes.
Device
is imported and run in a NodeJS environment.device.on('disconnect')
is emitted before raising a device.on('cancel')
event. This usually happens when the caller cancels the incoming call before the SDK accepts it.sao-paolo
is expected as an edge name instead of sao-paulo
.SHAKEN/STIR Verification Status for incoming calls
Twilio Client's Connection class now has Connection.callerInfo.isVerified
, that can be used to display a trust indicator to the recipient when an incoming call, say from the public telephone network, has been verified under the SHAKEN/STIR framework.
A verified call that has been given highest attestation under SHAKEN/STIR means that the carrier that originated the call both (1) knows the identity of the caller, and (2) knows the caller has the right to use the phone number as the caller ID.
When your application receives a request webhook, that has the new StirStatus
parameter all you have to do is <Dial><Client>
and Twilio will implicitly pass the StirStatus
to the JavaScript Client.
CallerInfo
The Connection.callerInfo
field returns caller verification information about the caller. If no caller verification information is available this will return null
.
1class Connection {2// ...3callerInfo: CallerInfo | null;4}
A CallerInfo provides caller verification information.
1interface CallerInfo {2isVerified: boolean;3}
Attributes
isVerified
- Whether or not the caller's phone number has been attested by the originating carrier and verified by Twilio using SHAKEN/STIR. True if the caller has been verified at highest attestation 'A', false if the caller has been attested at any lower level or verification has failed.Example:
1device.on('incoming', connection => {2if (connection.callerInfo && connection.callerInfo.isVerified) {3console.log('This caller is verified by a carrier under the SHAKEN and STIR call authentication framework');4}5});
Read here to learn more about making and receiving SHAKEN/STIR calls to/from the public telephone network.
Twilio Edge Locations
This release includes support for the expansion of Twilio's Global Infrastructure via Edge Locations which allows connectivity control into and out of Twilio's platform. The Voice Client JS SDK uses these Edges to connect to Twilio's infrastructure via the new parameter Twilio.Device.Options.edge
. This new parameter supersedes the now deprecated Twilio.Device.Options.region
. See Twilio.Device.Options.edge
API documentation for migration instructions.
Example
const device = new Device(token, { edge: 'ashburn' });
**Support for automatic Edge location Fallback (Beta) ###
Deployments designed to connect to multiple Twilio Edge locations can take advantage of the new fallback mechanism. To enable the edge fallback, specify an array of edge names via Twilio.Device.Options.edge
. When enabled and a connection failure is encountered, the SDK will reattempt the connection to the next region in the list. For more details about how the fallback works, refer to Edge Fallback documentation.
Example
const device = new Device(token, { edge: ['ashburn-ix', 'san-jose-ix', 'roaming' ] });
Application Name and Version Logging Support
This release also introduces two new Device
options: appName
and appVersion
. The values will be logged to Insights. These can be used to correlate other insights events with the application generating them. This is useful for debugging purposes in cases where multiple versions are deployed e.g. When performing A/B testing.
Support for Microsoft Edge Legacy is now deprecated. Running device.setup()
on this browser will result with the console warning below. See this advisory for more information about important upcoming breaking changes with Microsoft Edge Legacy.
1Microsoft Edge Legacy (https://support.microsoft.com/en-us/help/4533505/what-is-microsoft-edge-legacy)2is deprecated and will not be able to connect to Twilio to make or receive calls after September 1st, 2020.3Please see this documentation for a list of supported browsers4https://www.twilio.com/docs/voice/sdks/javascript#supported-browsers
rtcSample.rtt
raised by Connection.on('sample', rtcSample => ...)
was reported in seconds instead of milliseconds on Firefox and Chrome 81+. If your application converts rtcSample.rtt
to milliseconds on these browsers, please remove the conversion.Also with this release, we addressed an issue where certain device event handlers, when an exception is thrown, causes some connection event handlers to stop working. This causes potential side effects such as incoming ringtone not being able to stop after receiving a call.
Example
In the following example, connection.on('accept')
will not trigger if device.on('connect')
throws an error. With this fix, connection.on('accept')
handler should now receive the event.
1connection.on('accept', () => {2console.log('This is my "accept" handler.');3});45device.on('connect', () => {6throw 'Something went wrong.';7});
Events affected
The following are the events affected and should be fixed with this release.
Device Events | Affected Connection Events |
---|---|
device.on('connect') | connection.on('accept') |
device.on('error') | connection.on('error') |
device.on('cancel') | connection.on('cancel') |
device.on('disconnect') | connection.on('disconnect') |
More information about NodeJS Events
As mentioned in our public documentation, the Device and Connection objects are EventEmitters. This release doesn't change the default behavior of EventEmitters
, where if one of the handlers on the same EventEmitter
object throws an exception, the rest of the event handlers will not receive the event. Consider the following example.
1const myEmitter = new EventEmitter();23// Subscribe some event handlers4myEmitter.on('testevent', () => console.log('This is my handler 1'));5myEmitter.on('testevent', () => {6console.log('This is my handler 2');7throw 'Something went wrong';8});9myEmitter.on('testevent', () => console.log('This is my handler 3'));1011// Emit an event12myEmitter.emit('testevent');
In the above example, testevent
has three handlers and are on the same EventEmitter object myEmitter
. If one of the handlers, in this case handler number 2, throws an error, the rest of the event handlers will not receive the event. In this case, handler 3 will not receive testevent
. This is a normal behavior on EventEmitters
and this SDK release doesn't change this behavior. This release only fixes the issue where if the events are coming from two different EventEmitter
objects - Connection
and Device
.
Typescript declarations are now included with our NPM package. In the following example, Device
, Connection
, and their functions should have the correct typings.
1import { Device, Connection } from 'twilio-client';23const token = ...;4const deviceOptions = ...;5const device: Device = new Device(token, deviceOptions);67const connection: Connection = device.connect(...);8...9connection.disconnect();
Device.on('incoming')
event is not raised when the incoming sound is stopped right after playing it. This is a timing issue which can happen if multiple incoming connections comes in almost at the same time.This browser does not support audio output selection
. We now check if this is supported on the browser before attempting to update the output device.Added the ability to access the SDK logger instance using the loglevel npm module. Please refer to the loglevel documentation for a list of logger APIs.
For example, to set the log level:
1import { getLogger } from 'loglevel';23const logger = getLogger(Device.packageName);4// Set log level on subsequent page loads and refreshes5logger.setLevel('DEBUG');
Also in 1.10.0
Added an experimental feature for Chrome browser to enable
Aggressive ICE Candidate Nomination. This feature can be enabled by setting forceAggressiveIceNomination
to true. If your deployment is on devices with one network interface and your RTT to Twilio's Servers is typically greater than 96 milliseconds, this feature may help reduce call connect time. As this is an experimental feature, we don't recommend enabling this until after testing it thoroughly in your deployment.
Example:
1Device.setup(TOKEN, {2forceAggressiveIceNomination: true3});
sg1-ix
. See Twilio Client Regions for the list of supported regions. Note that with this release, to support new regions without requiring an SDK update, we have removed the check for the region name passed to Device.setup
. If an unsupported region is supplied, Device.on('error')
will be called.audioInputLevel
and audioOutputLevel
within the last second in the connection sample object Connection.on('sample', handler(sample))
.constant-audio-input-level
warning is not being emitted.sample.mos
, emitted from Connection.on('sample', handler(sample))
, is always null.Device.setup()
is invoked.Max Average Bandwidth API
By default, the Opus codec is set up with a transmission rate of around 32 kbps (40-50kbps on the wire). With this release, you are able to set a custom max average bitrate to better control how much bandwidth your VoIP application should use. See RFC-7587 section 7.1 for information about Max Average Bitrate.
The main purpose of this API is to set a lower max average bitrate to minimise bandwidth usage. This is particularly useful in deployments where bandwidth is at a premium. Where bandwidth is not of concern, you do not need to use this API. Max Average Bitrate can be set to as low as 6,000bps and as high as 51,000 bps. Values outside this range are ignored and the default Opus operation mode is used. See API Docs for more information.
As would be expected, lowering the max average bitrate impacts audio quality. We don't recommend setting max average bitrate to a value below 8,000 bps. On the other hand, setting values over 32,000 bps will have negligible audio quality improvements.
This is currently not supported in Firefox due to this bug.
Example, to set a new max average bitrate to 16,000 bps:
1Device.setup(TOKEN, {2codecPreferences: ['opus', 'pcmu'],3maxAverageBitrate: 16000,4});
Fixed an issue causing audio levels to be reported as zero when running as an extension, or when the browser tab is inactive or minimized.
Fixed an issue causing Connection.status()
to return pending
instead of closed
after calling Connection.reject()
.
Media Reconnection States and Events
This feature, when enableIceRestart
is enabled, allows for detecting when media connection fails which will trigger automatic media reconnection, and for detecting when media connection is restored.
New Events
We've added two new events that will fire when enableIceRestart
is enabled:
Connection.on('reconnecting', handler(error))
- raised when media connection fails and automatic reconnection has been started by issuing ICE Restarts. During this period, Connection.status()
will be set to reconnecting
.
error
- Error object { code: 53405, message: 'Media connection failed.' }
disconnect
and bytes sent and received in the last 3 seconds is zero.failed
. Only Chrome browser will attempt an ICE restart with this trigger. Other browsers will immediately disconnect the call and raise an error 31003
. This is due to browsers not fully supporting connection states during an ICE restart.Connection.on('reconnected', handler())
- raised when media connection has been restored which is detected when media starts flowing. Once reconnected, Connection.status()
will be set to open
.Retries
ICE restarts will be retried in the event that previous ICE restarts are unsuccessful. Retry attempts will happen when ICE Connection state or PeerConnection state transitions to failed
. If more than 30 seconds has elapsed during this transition, the call will disconnect and raise an error 31003
.
Device.version
to return sdk version1// Error object2{3code: number,4message: string,5...6// New twilioError property7twilioError: {8causes: Array<string>,9code: number,10description: string,11explanation: string,12solutions: Array<string>,13message: string,14stack: string15}16}
Automatic Media Reconnection
This feature was first introduced in 1.7.4 and was enabled by default.
With this release, we have introduced the enableIceRestart
reconnect flag to enable or disable Automatic Media Reconnection. The default is disabled. This will allow you to transition your code to utilise this feature. Example usage:
Twilio.Device.setup(TOKEN, { enableIceRestart: true });
Failed to set remote answer sdp: Called in wrong state: kStable
.Device.destroy()
now properly disconnects all connections.Updated July 16, 2019
The introduction of Automatic Media Reconnection in 1.7.4 is enabled by default. This functionality may affect program flow if you rely on Device.on('error', …) with error code 31003 to update your UI or reconnect logic. This error is not thrown at the time of media interruption any longer. It is now sent after ICE restart is attempted and fails which may take 10s of seconds.
Automatic Media Reconnection
A call may be inadvertently disconnected when media is temporarily lost. With this release, we will attempt to reconnect the media before dropping the call with a process known as ICE restart.
If you are relying on Device.on('error', …) with error code 31003, to update your UI or to initiate a reconnect, you will need to update your code to use Device.on('offline', …) instead. The 31003 error code may not be reported for some time as ICE restarts are continually attempted.
Updated July 16, 2019
We have identified this as a potential breaking change and we will include an opt-in feature switch in the 1.7.6 release. We apologize for any inconvenience this may have caused you.
failed
state, rather
than immediately disconnecting.RTCSample
).Connection.mute()
will not work.The following known issues will be addressed in an upcoming 1.7.5 release:
Connection.codec
, which will be populated with the audio codec used
in the call as soon as the SDK receives that information from WebRTC. We currently do not get the audio
code from FireFox.RTCSample
) every second through a new event, Connection.on('sample')
.Device.audio.setInputDevice
and then later calling Device.audio.setInputDevice
during the call
when using a browser that supports the unified-plan SDP semantic.Unified Plan support
Updated the algorithm used to report the "Audio input level" and "Audio output level" Insight metrics. The levels are obtained directly from an AudioContext and are no longer read out from webrtc's legacy stats.
Opus and Codec Preference API
Opus and PCMU are the two codecs now offered and accepted by Twilio Client JS.
For 1.7 and all further 1.x releases, G.711 (PCMU) will continue to be the preferred codec offered. This is to avoid any potential breaking changes.
The following code illustrates how to make Opus the default codec:
1var device;2// Setup Twilio.Device3device = new Twilio.Device(YOUR_TOKEN, {4codecPreferences: ['opus', 'pcmu']5});
The option codecPreferences can be passed in when instantiating a Device
instance or when calling Device.setup
to set the codecs preference. The default is set to [‘pcmu', ‘opus']
dscp
flag to be ignored in Chrome due to a breaking change introduced in Chrome M72.Added device.audio.setAudioConstraints()
and device.audio.unsetAudioConstraints()
. These methods allow setting a MediaTrackConstraints object to be applied to every time device.audio.setInputDevice()
is called, and any time an active input device is lost and the SDK gets new user media to fall back to another input device. If an input device is already set via device.audio.setInputDevice()
, these methods will immediately call setInputDevice()
internally and return the resulting Promise, otherwise they will return a resolved Promise. The currently set audio constraints can be seen on the new read-only field, device.audio.audioConstraints
, which defaults to null
.
Example:
1device.audio.setAudioConstraints({ echoCancellation: true });2await device.audio.setInputDevice('default');3// Now we have a live input audio track, opened with echoCancellation:true4device.audio.setAudioConstraints({5autoGainControl: false,6echoCancellation: false,7noiseSuppression: false,8}).then(() => {9// We successfully applied the new constraints and should automatically hear the difference.10// Future calls to setInputDevice will also use these constraints until they're cleared.11}, err => {12// Something went wrong, most likely err is an OverconstrainedError. Let's roll back.13await device.audio.unsetAudioConstraints();14// We should now have a working input audio track again15});
Browser Support
Twilio.Device.audio.disconnect()
will now toggle whether the disconnect sound should play while already on an active call.backoffMaxMs
option to Device.setup()
options that takes a time in milliseconds to override this default. The minimum allowable value is 3000ms. Example:Device.setup(token, { backoffMaxMs: 45000 });
ws
dependency to latest. Only affects npm package because the CDN artifact of twilio.js uses
the browser's WebSocket implementation.network-change
, which is sent first when a new Connection is established, and then any time thereafter the network information changes. See the support browser chart here.{ fakeLocalDTMF: true }
that uses imitation DTMF sounds instead of the default real DTMF sounds, preventing an issue where DTMF tones would sometimes register twice.plan-b
is deprecated.rtcConfiguration
field to IDeviceOptions, which takes an RTCConfiguration object that gets passed to any created RTCPeerConnections. Example:1Device.setup(token, {2rtcConfiguration: { iceTransportPolicy: 'relay' },3});
device.disconnectAll()
,
will no longer throw an exception or pause script execution.closeProtection
featureon
, addListener
, removeListener
, etc...) to Device singleton.allowIncomingWhileBusy
. When set to true, Device's default behavior of silently ignoring the incoming call is removed, and the incoming call will instead cause Device to emit an "incoming" event. If accepted, the prior active call will be immediately disconnected, and the incoming call will be accepted, replacing the prior active call.Twilio.Device.setup(token, { allowIncomingWhileBusy: true });
Map<string, string> Connection.customParameters
. When a TwiML application sends custom parameters using the <Parameter>
noun, these parameters will be added to Connection.customParameters
. For example:1<?xml version="1.0" encoding="UTF-8"?>2<Response>3<Dial>4<Client>5<Identity>alice</Identity>6<Parameter name="foo" value="bar"/>7<Parameter name="baz" value="123"/>8</Client>9</Dial>10</Response>
1device.on('incoming', connection => {2assert.equal(connection.customParameters.get('foo'), 'bar');3assert.equal(connection.customParameters.get('baz'), '123');4});
Note that the following restrictions apply to the Parameter noun:
plan-b
is deprecated.Twilio.Device
may now be instantiated multiple times viaconst device = new Twilio.Device(token, options);
Twilio.Device.setup()
may now be called with Access Tokens, in addition to Capability Tokens.Twilio.Device.destroy()
will now completely clear out the Device, allowing Device.setup()
to be called with a new set of options.on(eventName, handler)
and .removeListener(eventName, handler)
, replacing our legacy handlers (such as .accept(handler)
, .error(handler)
, etc...). The following methods are deprecated:1Device.cancel(handler)2Device.connect(handler)3Device.disconnect(handler)4Device.error(handler)5Device.incoming(handler)6Device.offline(handler)7Device.ready(handler)8Connection.accept(handler)9Connection.cancel(handler)10Connection.disconnect(handler)11Connection.error(handler)12Connection.ignore(handler)13Connection.mute(handler)14Connection.reject(handler)15Connection.volume(handler)
These have been replaced with the following EventEmitter events:
1Device.on('cancel', handler)2Device.on('connect', handler)3Device.on('disconnect', handler)4Device.on('error', handler)5Device.on('incoming', handler)6Device.on('offline', handler)7Device.on('ready', handler)8Connection.on('accept', handler)9Connection.on('cancel', handler)10Connection.on('disconnect', handler)11Connection.on('error', handler)12Connection.on('mute', handler)13Connection.on('reject', handler)14Connection.on('volume', handler)
Note that there is no Connection#ignore
event. The Connection.ignore(handler)
method is actually a backward-compatible listener for the Connection.on('cancel', handler)
event.
plan-b
is deprecated.Twilio.Device.isSupported
flag which should be true
if WebRTC or ORTC is supported. If false
, it's an indicator that Device.setup
should not be called (it will throw an unsupported exception).Device.audio.setInputDevice()
Device.audio.speakerDevices
or Device.audio.ringtoneDevices
.ringing
Connection state and Connection#ringing
event behind the flag: Twilio.Device.setup(token, { enableRingingState: true })
. With both the answerOnBridge
property and the enableRingingState
flag enabled in the consuming application, the Connection state is now be more granular:
ringing
state is transitioned to when the callee is notified of the incoming callopen
state is transitioned to when the callee has accepted the callDevice.sounds
deprecation warning to be logged every time the library was loaded.HTMLAudioElement.setSinkId
Device.setup
is called with a new token.Device.activeConnection()
to return the first received incoming call when there is no active, ongoing call. This behavior was inadvertently changed in 1.4.15. In the next breaking release, this behavior will be changed so that Device.activeConnection()
correctly only returns the active connection if one exists.de1
connection.getRemoteStream()
and connection.getLocalStream()
to retrieve the local/remote streams being used in the Connection.Device.audio.setInputDevice
has been completely disabled in Firefox.Device.audio.unsetInputDevice()
will no longer throw an error if called synchronously inside the Device.incoming()
handler.Device.audio.inputDevice
is null and there is no active call, all user media should be properly released.Device.audio.setInputDevice()
will now work properly when the client is the caller or callee.Device.incoming
handler will still be fired after a maximum timeout of 2 seconds.
Pausing execution (by adding an alert or prompt on Device.incoming) should no longer prevent the incoming ringing sound from playing. Note that in Chrome, the incoming sound will only play once as the paused script execution will prevent looping behavior.ie1-tnx
eval
statements; no more should be present.Connection#error
, when ICE liveliness checks fail. This serves to differentiate between fatal and non-fatal ICE events.us1
and us2
on TNX connection provisioning is supported in Client via us1-tnx
and us2-tnx
region parameters, respectively.Device.setup
is called with a new token.