A Ruby library for interacting with FreeSWITCH through mod_event_socket, using async I/O.
- Prerequisites
- Installation
- Inbound Listener
- Outbound Listener
- Starting Listeners
- Closing Connections
- Command Socket
- Configuration
- Event Socket Protocol
- API Documentation
- License
You should be familiar with mod_event_socket and the differences between inbound and outbound event sockets before getting started.
Requires Ruby 3.0+.
Add to your Gemfile:
gem "librevox"Subclass Librevox::Listener::Inbound to create an inbound listener. It connects to FreeSWITCH and subscribes to events.
React to events in two ways:
- Override
on_event, called for every event. - Use
eventhooks for specific event names.
class MyInbound < Librevox::Listener::Inbound
def on_event(e)
puts "Got event: #{e.content[:event_name]}"
end
event :channel_hangup do
do_something
end
# The hook block receives a Response when it takes an argument:
event :channel_bridge do |e|
puts e.content[:caller_caller_id_number]
end
def do_something
# ...
end
endBy default, inbound listeners subscribe to all events. Use events to limit which events are received, and filters to filter by header values:
class MyInbound < Librevox::Listener::Inbound
events ['CHANNEL_EXECUTE', 'CUSTOM foo']
filters 'Caller-Context' => ['default', 'example'],
'Caller-Privacy-Hide-Name' => 'no'
endSubclass 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.
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:
class MyOutbound < Librevox::Listener::Outbound
def session_initiated
answer
digit = play_and_get_digits "enter-digit.wav", "bad-digit.wav"
bridge "sofia/gateway/trunk/#{digit}"
end
endApplications that read input (like play_and_get_digits and read) return the collected value directly.
def session_initiated
answer
set "foo", "bar"
multiset "baz" => "1", "qux" => "2"
playback "welcome.wav"
hangup
endFor apps not yet wrapped by a named helper, call application directly:
application "park"Channel variables are available through session (a hash) and variable:
def session_initiated
answer
number = variable(:destination_number)
playback "greeting-#{number}.wav"
endTo avoid name clashes between applications and commands, commands are accessed through api:
def session_initiated
answer
api.status
api.originate 'sofia/user/coltrane', extension: "1234"
endStart a single listener:
Librevox.start MyInboundWith connection options:
Librevox.start MyInbound, host: "1.2.3.4", port: 8021, auth: "secret"Start multiple listeners:
Librevox.start do
run MyInbound
run MyOutbound, port: 8084
endDefault ports are 8021 for inbound and 8084 for outbound.
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. Use done (alias for close_connection_after_writing):
class MyOutbound < Librevox::Listener::Outbound
event :channel_hangup do
done
end
endLibrevox::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.closeLibrevox.options[:log_file] = "librevox.log" # default: STDOUT
Librevox.options[:log_level] = Logger::DEBUG # default: Logger::INFOWhen started with Librevox.start, sending SIGHUP to the process reopens the log file, making it compatible with logrotate(1).
Understanding the outbound event socket protocol is important for working on librevox internals.
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
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
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
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.
Librevox runs two fibers for each outbound connection:
- Session fiber (
run_session) — runs the setup sequence and thensession_initiated. Eachcommandorapplicationcall blocks the fiber until the reply arrives. - Read fiber (
read_loop) — reads messages from the socket and dispatches them toAsync::Queueinstances, waking the session fiber.
An Async::Semaphore(1) mutex on command ensures only one command is
in-flight at a time, so replies are always delivered to the correct caller.
This also serializes commands issued by event hooks (which run in their own
fibers) with the main session flow.
Applications and commands are documented with YARD. Generate docs with:
yard doc
See Librevox::Applications and Librevox::Commands for the full API reference.
MIT. See LICENSE for details.