Skip to main content

Overview#

The term call control covers the integration between a headset and softphone software on a PC that involve incoming and outgoing calls. Some of the possible integration points are:

  • Reflecting the state of a call (active call, hold, inactive).
  • Reflecting the microphone mute state (muted or unmuted).
  • Reacting to signals coming from a device (e.g. button presses):
    • Accepting or rejecting calls.
    • Putting a call on hold or resuming a held call.
    • Muting or unmuting the microphone.
    • Redial or dial.
  • Setting the ringer on or off (indicated by either audio or blinking LEDs on the device).
See code sampleTry demo

Prerequisites#

Initializing the Call Control module#

The rest of this tutorial refers to the CallControl module, which is rather low-level and is therefore considered advanced.

In order to utilize the call control functionality, you first need to initialize the module. This is done via the CallControlFactory class. The class provides helper functions for taking ISdkDevice instances and giving you access to their call control functionality.

To initialize, you must create a CallControlFactory instance by providing the unique SoftphoneInfo information of your software product. This includes specifying the name of your product, and giving a unique UUID identifier, and is required to use call control functionality.

You can follow the Using SDK modules guide to see how to initialize the Call Control module.

Once the module is initialized, you can take any ISdkDevice instance to create a ICallControl object, which you can then use for any call control functionality (for that device).

The code snippet for that is also in the Using SDK modules guide.

For the rest of this tutorial, whenever we talk about a devices' call control functionality, we will assume you have created an ICallControl instance for that device.

Call lock#

The first step in being able to change any of the devices' states is acquiring a call lock. This is a hard requirement, which makes sure that two softphone products are not clashing with each other and simultaneously using a headset.

Once a call is finished, it is important to release the call lock. That way, other softphone products (or subsequent phone calls to your product) can successfully acquire (or reacquire) the call lock for the device.

Depending on the situation, the lock might already be acquired (usually when executing multiple features in quick succession), so an internal check for the lock should always be the first step.

[Note] If acquiring the call lock fails, it is usually because another softphone is currently using the device.

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device a ->> b: device.takeCallLock() alt lock not acquired b ->> a: not acquired Note over a: Cannot proceed else lock acquired b ->> a: lock acquired Note over a: Can proceed a ->> b: any call control feature b ->> c: execute call control feature Note over a: When done with call a ->> b: device.releaseCallLock() end

It is up to the third party developer to keep internal track of the status of the call lock:

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device a ->> a: Check for call lock alt Does not have call lock Note over a,c: Must acquire call lock first else Already has call lock Note over a, c: Can proceed immediately a ->> b: any call control feature b ->> c: execute call control feature Note over a: When done with call a ->> b: device.releaseCallLock() end

How to acquire a call lock#

// 1. Initialize library
// 2. Acquire connected devices
// Create a `ICallControl` object from an arbitrary device
const deviceCallControl = await callControlFactory.createCallControl(jabra.deviceList.value[0]);
let doesDeviceHaveLock = false;
try {
let doesDeviceHaveLock = await deviceCallControl.takeCallLock();
} catch (err) {
alert(err); // will throw if you already have the call lock
}
if (doesDeviceHaveLock) {
// You can now set device states, e.g. callControl.setMute(true)
}

How to release a call lock#

try {
deviceCallControl.releaseCallLock();
} catch (err) {
alert(err); // will throw if you did not have the call lock
}

Summary#

  1. Make sure to acquire a lock for the device.

  2. Once acquired, you can synchronize states between your app, the library, and the Jabra headset.

  3. Once the call ends, make sure to release the lock.

Supported features#

Set Ringer#

Notifies the device that there is an incoming call. Effect depends on the capabilities of the device, usually a flashing green LED.

[Note] Toggling the ringer on a device requires a call lock before being used.

[Note] The ring effect might differ depending on whether the device is in an active call.

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note over a: [Prerequisite] Ensure call lock opt Turn on ringer a ->> b: device.ring(true) b ->> c: #20 Note over c: ring effect starts end opt Turn off ringer a ->> b: device.ring(false) b ->> c: #20 Note over c: ring effect stops end

Change call status (i.e. offHook)#

Notifies the device that a call was either started or ended. Effect depends on the capabilities of the device, usually a solid red LED while in a call.

[Note] Changing the call status on a device requires a call lock before being used.

[Note] Successfully changing the status of a call will result in an acknowledgement signal being sent by the device. Refer to the signalling section of this tutorial for more information.

[Note] The term offHook comes from the HID standard and relates to old phones that had a physical hook that was released whenever the handset was picked up by the user.

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note over a: [Prerequisite] Ensure call lock opt On call start a ->> b: device.offHook(true) b ->> c: #20 alt Device successfully changes state to 'in a call' Note over c: in-call effect starts c ->> b: HOOK_SWITCH signal b ->> a: device.deviceSignals emits else Device does not change state Note over a,c: No acknowledgement signal sent by device end end opt On call end a ->> b: device.offHook(false) b ->> c: #20 alt Device successfully changes state to 'not in a call' Note over c: in-call effect stops c ->> b: HOOK_SWITCH signal b ->> a: device.deviceSignals emits else Device does not change state Note over a,c: No acknowledgement signal sent by device end end

Mute / Unmute#

Notifies the device that the microphone should be muted or unmuted. Effect depends on the capabilities of the device.

[Note] Muting or unmuting a device requires a call lock before being used.

[Note] Some devices have a feature called "local mute". This means that muting happens on the device, i.e. the headset stops using the microphone completely.

[Note] Some devices come with a microphone that can be physically manipulated by the user. For instance, Jabra Evolve2 85 will be muted when the boom arm is raised. Muting or unmuting these devices programmatically can lead to out-of-sync states, where the microphone is "unmuted" but the boom arm is raised. This situation is described in more detail here.

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note over a: [Prerequisite] Ensure call lock opt Mute microphone a ->> b: device.mute(true) b ->> c: #20 Note over c: mic gets muted end opt Unmute microphone a ->> b: device.mute(false) b ->> c: #20 Note over c: mic gets unmuted end

Hold / Resume#

Notifies the device that the call state changed, but that the call has not ended yet. This means that the call was either put on hold, or was resumed. Effect depends on the firmware of the device.

[Note] Changing the call status on a device requires a call lock before being used.

[Note] Putting a call on hold is usually accompanied by starting a new call. Handling multiple calls is an advanced topic and is described in more detail here (TODO).

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note over a: [Prerequisite] Ensure call lock opt Hold call a ->> b: device.hold(true) b ->> c: #20 Note over c: on-hold effect starts end opt Resume call a ->> b: device.hold(false) b ->> c: #20 Note over c: in-call effect starts end

Reacting to device signals#

Jabra devices sends signals notifying you when something happens in the device. This could be:

  • the user expressing an intent to do something by interacting with the device. This could be pressing a button or raising the boom arm.
  • acknowledgement to a request (e.g. a SignalType.HOOK_SWITCH signal after your softphone starts a call successfully).
  • an error condition (e.g. headset out of range from the base station).

Reacting to button press signals#

See code sample

When the user presses a button on the device, a signal is sent back to the library and then your app. Reacting to a button press signal involves multiple steps:

  1. Receiving the signal.
  2. Interpreting if the signal was a button press or an acknowledgement of a state change request from the PC. This usually depends on the internal state of your softphone.
  3. Determining which button was pressed by the user.
  4. Reacting to the pressed button. Note that a button press should trigger a reaction.

[Note] Different signals report different values, i.e. either an absolute or a relative value. See the API documentation for more info.

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note right of c: user presses button c->>b: reports a new signal b->>a: device.deviceSignals emits Note over a: determine signal is a button Note over a: determine which button was pressed opt Accept call button Note over a: determine button value a ->> b: offHook(true | false) b ->> c: #20 Note right of c: start or end call end opt Mute button Note over a: determine button value a ->> b: mute(true) or mute(false) b ->> c: #20 Note right of c: mute or unmute end

Signals are emitted only in specific cases#

The library will emit signals that are sent from a device only in the following cases:

  • You have the call lock for the device (e.g. when in an active call). This will report any signal related to call control.

  • Your softphone does not have a call lock but is selected as the "Preferred Softphone" option in Jabra Direct. In this scenario, only SignalType.HOOK_SWITCH and SignalType.REDIAL signals will be reported to your softphone. The signal might come either from the user interacting with a device or from another softphone utilizing the device. If you can acquire a call lock for the device, it's the former case, and you should react on the user's intended action. If you can't acquire a call lock, it's the latter case - the signal is probably intended for another softphone, and you should ignore it.

Signals must be seen in context to the current operational state#

It is important to understand that you do not need to react to every signal received by the device; signals that are 'out of context' from the situation at hand should be ignored.

Signals are sent to all applications that connect to the headset. Your softphone application should therefore only react to signals that you are expecting, ignoring those that do not fit into the context of the current operational state of the softphone.

Here is an example for clarification:

If your softphone has an incoming call and executes a headset's ring command, then the expected signal from the headset is an acceptance, a rejection, or simply a ‘no answer’. Here, either of the three is an expected return signal. However, if your softphone does not have an incoming call, nor has it executed a ring command, then it should not react to any ‘hook-switch’ signal from the headset.

Situations with ‘out of context’ signals can happen for various reasons, for instance when other softphones are interacting with the headset. You are strongly advised against reacting to unexpected SignalType.HOOK_SWITCH signal events. Doing so is likely to cause out-of-sync issues between the device and the software. This behaviour is explained in more detail here.

Expected signals must trigger a reaction#

Once you have taken context into account and ensured that a signal is targeting your softphone, it is important to react to that signal.

When the device sends a signal, e.g. user presses the mute button, the signal does not itself change the state on your softphone. The device just passes the signal and it is then expected that the software reacts to it. However, the state on the device is already changed. For this reason, it is important that your solution handles each signal. Otherwise, the state in your softphone and on the device could become out of sync.

To illustrate with an example, receiving a SignalType.PHONE_MUTE signal means the user either muted or unmuted the microphone. For example, this could be done by physically manipulating the boom arm on a headset. If your solution does not react to this signal and execute ICallControl.mute with an appropriate parameter, the device will interpret that as refusal to acknowledge the change, and will internally revert to the previous state. This causes an out-of-sync, because the microphone could be in a physical state where it's muted (e.g. boom arm is raised), but it thinks it's unmuted. A subsequent user action to mute or unmute will not trigger a new signal, and your application will not know the "true" physical state of the microphone.

[Note] In this entire section, we assume you are in a state where your softphone expects said signal. As noted earlier, context matters and you should only react to signals when you are sure they are targeting your softphone.

For the given example of muting the microphone, the sequence diagram is as follows:

sequenceDiagram participant a as App participant b as Jabra library participant c as Jabra device Note right of c: User mutes their microphone c ->> b: reports a new signal b ->> a: device.deviceSignals emits Note over a: Determine it is button press Note over a: Determine it is the mute button Note over a: Determine new mute state opt new state is muted a ->> b: device.mute(true); b ->> c: #20 Note right of c: The device mutes Note over a: Update UI to reflect new state end opt new state is unmuted a ->> b: device.mute(false); b ->> c: #20 Note right of c: The device unmutes Note over a: Update UI to reflect new state end opt Ignore mute button Note over a, c: Mute state no longer in sync end

Reacting to HOOK_SWITCH#

An important note about the SignalType.HOOK_SWITCH signal. The device sends this signal in several scenarios:

  • as an acknowledgement of a successful ICallControl.offHook request made by your softphone (see above),

  • when the user is trying to accept an incoming call by interacting with the device, or

  • when the user is trying to start a new outgoing call by interacting with the device.

For that reason, it is important that you understand the context of the SignalType.HOOK_SWITCH signal:

  • If your softphone just started a new call, you will receive an acknowledgement, and should do nothing more.

[Note] If you don't get an 'acknowledge' response signal within a reasonable time limit, that means the command was rejected, the device didn't change its state, and the its inactivity in regards to responding should be interpreted as a 'NACK' (not acknowledged). For example, if you started a new call and didn't receive an acknowledgement, either the device refused to start a new call, or you didn't inform the device that a new call has started (via ICallControl.offHook(true)). This behaviour can be easily observed by executing ICallControl.offHook(true) twice, without any other commands in between. In that case, the second call will not be acknowledged, as the state of the device will not change.

  • If your softphone has an incoming call and asked the device to start ringing, receiving this signal means the user accepted the call by pressing a button on the device.

  • If your softphone is idling, this signal either means another softphone on the computer is trying to start a call, or that the user is trying to start an outgoing call on your softphone. As noted earlier, whether you should act on this case depends on whether your softphone can acquire a call lock for the device.

Additional details#

All Jabra devices have complex firmware that handle many different scenarios. This means that sending a command can have different effects depending on the context that the device is in.

As an example, if you send a mute command to a device but it does not consider itself in a call, the mute command does not have any effect. Similarly, if the user presses the mute button on the headset but the device is not in a call state, a mute signal is not sent from the device to the PC.

Additionally, some scenarios can depend on the type of hardware - a feature might not be available on a certain device.

Special cases#

Number pad#

Some devices come with a numeric keyboard on them for easier dialing, dealing with automated answering systems, or call forwarding. Examples include Jabra Dial 550 and Jabra Pro 9470.

If you use the Chrome Extension transport instead of WebHID, you should be aware that these buttons will not work correctly on Mac OS and Linux. The side effect can be summarized as follows:

This is due to known issues with the HID functionality provided by the system.