Behaviour for the module that handles each call's media.
ex_kamailio speaks the rtpengine protocol with Kamailio and hands each SDP to an implementation of this behaviour; it allocates no ports and picks no codecs. The handler binds its own sockets, advertises them in the SDP it returns, and does what it wants with the media.
defmodule MyApp.KamailioHandler do
use ExKamailio.CallHandler
@impl true
def init(_session, _opts), do: {:ok, %{}}
@impl true
def handle_offer(from_offerer_sdp, session, state), do: {:ok, to_answerer_sdp, state}
@impl true
def handle_answer(from_answerer_sdp, session, state), do: {:ok, to_offerer_sdp, state}
enduse ExKamailio.CallHandler defines overridable defaults for
handle_delete/2, handle_idle/2 and handle_info/3, leaving
init/2, handle_offer/3 and handle_answer/3 to the implementation.
Register it in config, bare or as {module, opts} to pass options to
init/2:
config :ex_kamailio, call_handler: MyApp.KamailioHandlerWithout it, ExKamailio.CallHandler.Default is used,
which returns each peer's SDP unchanged.
Call flow
Kamailio relays each SDP exchange as an rtpengine command, in a fixed order:
offer, then answer (either may be retransmitted), then delete. Peers are
named by their RFC 3264 roles: the offerer proposes SDP, the answerer
responds. In the initial INVITE, the only exchange supported so far, the
offerer is the caller.
init/2seeds the state.handle_offer/3gets the offerer's SDP and returns the offer the answerer sees, typically advertising a socket the handler just bound.handle_answer/3gets the answerer's SDP. The SDP it returns is the answer the offerer gets.handle_delete/2runs when Kamailio sendsdelete, to release what was allocated. The shipped config sends it onBYEonly; a cancelled or rejectedINVITEleaves the call to the idle timeout.
Every callback gets the ExKamailio.Session, which fills up with the SDPs
as the call progresses. A retransmitted offer or answer is answered with
the SDP the callback returned the first time, without running it again. A
callback that raises or returns the wrong shape kills the call process;
Kamailio gets an error reply and handle_delete/2 does not run.
Optional callbacks
handle_info/3gets messages sent to the call process, e.g. by aMembrane.Pipeline. The default logs them at debug level.handle_idle/2runs when no command arrives for:idle_timeout(default 30 min). The default returns{:stop, state}, which runshandle_delete/2and stops the process;{:ok, state}keeps the call. Stopping only frees the process; the SIP dialog goes on.
Callback deadline
Kamailio waits rtpengine_tout_ms (default 1000 ms) for each reply. A
single late reply makes it mark ex_kamailio as down for
rtpengine_disable_tout (default 60 s), and every call in that window
fails on the spot, whether ex_kamailio has recovered or not.
That is why a callback gets :callback_timeout (default 800 ms). Past it,
ex_kamailio replies with an error while Kamailio is still listening and
tears that one call down, handle_delete/2 included. Losing one call
beats losing a minute of all of them, so keep slow work out of callbacks.
The remaining 200 ms cover serialization and the round trip to Kamailio.
Over loopback that is plenty; across a network it may not be, so raise
rtpengine_tout_ms or lower :callback_timeout.
Summary
Functions
Declares the behaviour and the overridable defaults listed above.
Types
@type state() :: term()
Callbacks
@callback handle_answer(answer :: ExSDP.t(), ExKamailio.Session.t(), state()) :: {:ok, reply :: ExSDP.t(), state()}
@callback handle_delete(ExKamailio.Session.t(), state()) :: :ok
@callback handle_idle(ExKamailio.Session.t(), state()) :: {:ok, state()} | {:stop, state()}
@callback handle_info(message :: term(), ExKamailio.Session.t(), state()) :: {:ok, state()}
@callback handle_offer(offer :: ExSDP.t(), ExKamailio.Session.t(), state()) :: {:ok, reply :: ExSDP.t(), state()}
@callback init(session :: ExKamailio.Session.t(), opts :: keyword()) :: {:ok, state()}