Protocols in Liquidsoap
When Liquidsoap plays a track, it doesn’t just magically appear — it has to be resolved from somewhere. That “somewhere” could be a local file, a remote URL, a database entry, or even something generated on the fly.
That’s where protocols come in. Protocols are little rules that tell Liquidsoap:
“If you see a request in the form
protocol:arguments, here’s how to turn it into a real file or stream you can play.” 🎯
For example:
http://www.example.com/song.mp3
say:Hello world!
s3://my-bucket/path/to/file.mp3
In each case, the prefix before the : is the protocol,
and the part after it is the argument passed to your resolver code.
Built-in and custom protocols
Liquidsoap already ships with many ready-made protocols, written in the Liquidsoap scripting language. You can explore them in the protocol reference. But the real power comes when you define your own.
The anatomy of a protocol
Every protocol is defined by a handler function. The handler:
- Accepts the protocol arguments and some extra helper parameters.
- Returns a single resolved URI
- Can call other protocols in sequence, building a chain of transformations.
The function always gets two special variables:
~rlog→ A logging function. Use it to write debug or info messages that stay attached to the request.~maxtime→ A UNIX timestamp after which your resolver should give up.
The process.uri helper 🛠️
Before diving into the examples, it’s important to understand
process.uri, a convenient helper for
creating URIs of the form:
process:<binary> <arguments>
When Liquidsoap encounters such a URI, it will automatically execute
the given command and cancel it if it exceeds ~maxtime.
If you provide a uri argument, Liquidsoap will first
resolve that URI to a local file. Your command can then use two
placeholders:
$(input)→ replaced with the local file resolved from theuriargument (only ifuriis provided).$(output)→ replaced with the path to a temporary file whose extension is taken from theextnameargument.
⚠️ The output file is created empty before the command runs, to prevent race conditions on file ownership. This means your process must be able to overwrite it.
By using process.uri, you can safely wrap external
commands in a way that’s time-bound, predictable, and integrates
smoothly into Liquidsoap’s request resolution chain.
Example 1 — Fetching from S3 ☁️
Let’s say your files live on Amazon S3, and you want Liquidsoap to fetch them on demand:
def s3_protocol(~rlog, ~maxtime, arg) =
extname = file.extension(leading_dot=false, dir_sep="/", arg)
process.uri(extname=extname,
"aws s3 cp s3:#{arg} $(output)")
end
protocol.add("s3", s3_protocol,
doc="Fetch files from S3 using the AWS CLI",
syntax="s3://bucket/path/to/file")Now a request like:
s3://my-bucket/song.mp3
will be downloaded locally and returned as the playable URI.
Example 2 — Database lookup 📀
Protocols can also be dynamic. For instance, you might store file paths in a database keyed by track IDs:
def db_lookup_protocol(~rlog, ~maxtime, arg) =
string.trim(process.read("psql -t -c 'SELECT path FROM tracks WHERE id=#{int_of_string(arg)};'"))
end
protocol.add("db_lookup", db_lookup_protocol,
doc="Fetch file path from database by track ID")Now you can request:
db_lookup:42
and Liquidsoap will resolve it via your database.
Example 3 — Adding preprocessing
Want to normalize audio before playing? Or apply a voice-over?
def normalize_protocol(~rlog, ~maxtime, arg) =
process.uri(extname="wav",
uri=arg,
"normalize-audio $(input) $(output)")
end
protocol.add("normalize", normalize_protocol,
doc="Normalize audio levels before playback")You can chain protocols too:
normalize:cue_cut:s3://my-bucket/file.mp3
Liquidsoap will fetch from S3 → cut the segment → normalize it → play.
Chaining magic
The real beauty of protocols is chaining. Each protocol resolves to a single URI, which can then be handed off to the next one in the chain. This means you can build complex request pipelines:
voiceover:normalize:db_lookup:1234
💡 Here, db_lookup fetches a path,
normalize evens out the audio, and voiceover
mixes in an announcement.
Tips for writing robust protocols
Always respect
~maxtimeto avoid long-hanging processes.Use
~rloggenerously for debugging:rlog("Downloading from S3: #{arg}")Keep your commands secure — if you interpolate
arginto a shell command, validate or escape it.Test each piece of the chain independently before combining them.
By mastering protocols, you’re not just telling Liquidsoap where to find your content — you’re designing the path it takes to get there. That’s a superpower in streaming workflows, letting you pull from anywhere, process in any way, and still keep things flowing smoothly. 🚀