Thursday, March 31, 2022

Implementation of 2-stage ringback (WhatsApp - like) on Asterisk

 I'm pretty sure most of us nowadays get familiar with 2-stage ringback on different OTT (Over-The-Top, yes it's a sort of common name) applications. Like Skype/WhatsApp/Telegram. It's when you have 1 ringback when the remote device is "search" or "trying" state (like locating, receiving push-notification) and actually "ringing" state. 

And it's a handy thing to indicate these processes for the caller side

With a small hack, it's possible to do on Asterisk as well.

So, the overall schema will look like

Client A            Asterisk        SIP Proxy       Client B

    |   INVITE         |                |              |
    | ---------------> |   INVITE       |              |
    |                  | -------------> |   INVITE     |
    |                  |   100 - Trying | -----------> |
    |   Ringback 1     | <------------- |              |
    | <--------------- |                |              |
    |                  |                | 180 - Ringing|
    |                  |  180 - Ringing | <----------- |
    |   Ringback 2     | <------------- |              |
    | <--------------- |                |              |
    |                  |                |              |
    |                  |                |              |

Between INVITE and 180 - Ringing on Client B can pass some seconds in case of a mobile device. I'm not touching here on how to make this schema on SIP Proxy, but not much changed since this presentation in 2015.

So, Asterisk recipe:

extensions.conf

[main_dial]
exten => _X.,1,Progress()
 same => n,Playtones(trying)
 same => n,Dial(PJSIP/${EXTEN}@my_trunk&Local/keep_ringback@keep_ringback)

[keep_ringback]
exten => keep_ringback,1,NoOp

indicators.conf

...
trying = 425/90,0/75,525/90,0/250,425/90,0/75,525/90,0/3000


The idea here is to add one more channel to Dial() command, which will die instantly after the start. But somehow will keep the Playtones() working.

Looks really hacky, but works. But also I've found a problem, that will get into conflict with Queue() command with r(ring) option in a case if queue members are defined as Local channels and got into this schema as well. So, a bit updated version of this script would look like

extensions.conf

[main_dial]
exten => _X.,1,Progress()
 same => n,Playtones(trying)
 same => n,Set(KEEP_RINGBACK=)
 same => n,ExecIf($[ "${IS_CALL_WITHIN_QUEUE}" == "yes" ]?Set(KEEP_RINGBACK=&Local/keep_ringback@keep_ringback))
 same => n,Dial(PJSIP/${EXTEN}@my_trunk${KEEP_RINGBACK})

This was done on Asterisk 13 (which is not supported at the moment), maybe on newer version of Asterisk this one will stop working.

Upd: confirmed it's needed and working on Asterisk 18