Skip to content

Repository files navigation

Librevox

A Ruby library for interacting with FreeSWITCH through mod_event_socket, using async I/O.

Documentation: https://relatel.github.io/librevox

Table of Contents

Prerequisites

You should be familiar with mod_event_socket and the differences between inbound and outbound event sockets before getting started.

Requires Ruby 3.0+.

Installation

Add to your Gemfile:

gem"librevox"

Inbound Listener

Subclass Librevox::Listener::Inbound to create an inbound listener. It connects to FreeSWITCH and subscribes to events.

Events

React to events in two ways:

  1. Override on_event, called for every event.
  2. Use event hooks for specific event names.
classMyInbound < Librevox::Listener::Inbounddefon_event(e)puts"Got event: #{e.content[:event_name]}"endevent:channel_hangupdodo_somethingend# The hook block receives a Response when it takes an argument:event:channel_bridgedo |e|
putse.content[:caller_caller_id_number]enddefdo_something# ...endend

Event Filtering

By default, inbound listeners subscribe to all events. Use events to limit which events are received, and filters to filter by header values:

classMyInbound < Librevox::Listener::Inboundevents['CHANNEL_EXECUTE','CUSTOM foo']filters'Caller-Context'=>['default','example'],'Caller-Privacy-Hide-Name'=>'no'end

Note on CUSTOM events: FreeSWITCH custom events have a subclass name (e.g. CUSTOM conference::maintenance). You must include both the event name and subclass — events ['CUSTOM conference::maintenance']. Using just events ['CUSTOM'] will not match any custom events.

Outbound Listener

Subclass Librevox::Listener::Outbound to create an outbound listener. FreeSWITCH connects to it when a call hits a socket application in the dialplan.

Outbound listeners have the same event functionality as inbound, but scoped to the session.

Dialplan

When FreeSWITCH connects, session_initiated is called. Build your dialplan here.

Each application call blocks until FreeSWITCH signals completion (CHANNEL_EXECUTE_COMPLETE), so applications execute sequentially:

classMyOutbound < Librevox::Listener::Outbounddefsession_initiatedanswerdigit=play_and_get_digits"enter-digit.wav","bad-digit.wav"bridge"sofia/gateway/trunk/#{digit}"endend

Applications that read input (like play_and_get_digits and read) return the collected value directly.

defsession_initiatedanswerset"foo","bar"multiset"baz"=>"1","qux"=>"2"playback"welcome.wav"hangupend

For apps not yet wrapped by a named helper, call application directly:

application"park"

Channel variables are available through session (a hash) and variable:

defsession_initiatedanswernumber=variable(:destination_number)playback"greeting-#{number}.wav"end

API Commands

To avoid name clashes between applications and commands, commands are accessed through api:

defsession_initiatedanswerapi.statusapi.originate'sofia/user/coltrane',extension: "1234"end

Starting Listeners

Start a single listener:

Librevox.startMyInbound

With connection options:

Librevox.startMyInbound,host: "1.2.3.4",port: 8021,auth: "secret"

Start multiple listeners:

Librevox.startdorunMyInboundrunMyOutbound,port: 8084end

Default ports are 8021 for inbound and 8084 for outbound.

Closing Connections

After a session ends (e.g. the caller hangs up), the outbound socket connection to FreeSWITCH remains open for post-session events. Close it manually when done to avoid lingering sessions:

classMyOutbound < Librevox::Listener::Outboundevent:channel_hangupdodisconnectendend

Command Socket

Librevox::CommandSocket connects to the FreeSWITCH management console for one-off commands:

require"librevox/command_socket"socket=Librevox::CommandSocket.new(server: "127.0.0.1",port: 8021,auth: "ClueCon")socket.originate'sofia/user/coltrane',extension: "1234"#=> #<Librevox::Protocol::Response ...>socket.status#=> #<Librevox::Protocol::Response ...>socket.close

Configuration

Librevox.options[:log_file]="librevox.log"# default: STDOUTLibrevox.options[:log_level]=Logger::DEBUG# default: Logger::INFO

Event Socket Protocol

Understanding the outbound event socket protocol is important for working on librevox internals.

Outbound session lifecycle

When FreeSWITCH hits a socket application in the dialplan, it connects to the outbound listener. The listener sends three setup commands before any application logic runs:

Listener → FS: connect
FS → Listener: (channel data — becomes @session)
Listener → FS: myevents
FS → Listener: command/reply +OK
Listener → FS: linger
FS → Listener: command/reply +OK → triggers session_initiated

sendmsg and application execution

When an application (e.g. answer, playback, bridge) is executed via sendmsg, FreeSWITCH always sends the command/reply +OK immediately — it is an acknowledgement that the sendmsg was received, not that the application finished. Application completion is signalled by a CHANNEL_EXECUTE_COMPLETE event:

Listener → FS: sendmsg <uuid>
call-command: execute
execute-app-name: playback
execute-app-arg: welcome.wav
event-lock: true
FS → Listener: command/reply +OK ← immediate ack
FS → Listener: CHANNEL_EXECUTE event ← app started
...app is running...
FS → Listener: CHANNEL_EXECUTE_COMPLETE event ← app finished

event-lock

The event-lock: true header serializes application execution on the channel. It does not change what is sent back on the socket.

Without event-lock, if multiple sendmsg commands are pipelined, FreeSWITCH may dequeue and start executing the next application before the current one finishes. With event-lock: true, FreeSWITCH sets an internal flag (CF_EVENT_LOCK) on the channel that prevents the next queued sendmsg from being processed until the current application completes.

Two fibers per connection

Librevox runs two fibers for each connection:

  • Session fiber (run_session) — runs the setup sequence and then session_initiated. Each send_message or application call creates an Async::Promise, pushes it onto an array, and blocks the fiber until the promise is resolved.
  • Read fiber (each_message) — reads messages from the socket and resolves promises in FIFO order, waking the session fiber.

No mutex is needed — Ruby's cooperative fiber scheduling guarantees that the promise push happens before the I/O yield point (the socket write), so interleaving from concurrent event-hook fibers is safe. When a connection drops, pending promises are rejected with ConnectionError.

API Documentation

Applications and commands are documented with YARD. Generate docs with:

yard doc

See Librevox::Applications and Librevox::Commands for the full API reference.

License

MIT. See LICENSE for details.

About

ruby library for interacting with freeswitch

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages