From 21115c53f1cdc179f5fe55a38724abba208cda7d Mon Sep 17 00:00:00 2001 From: Loxbie <ole@freirad.at> Date: Tue, 2 Apr 2024 15:47:06 +0200 Subject: [PATCH] Refactor: Replace hardcoded I/O with unit-based approach This replaces the code for most of the in and output definitions. The are now read through a yaml config file where they are defined as a list of devices/endpoints. #71, #60, #41, #36 --- src/engine.liq | 3 +- src/library.liq | 49 +++--- src/out_soundcard.liq | 39 ++--- src/out_stream.liq | 321 +++++++++++++--------------------------- src/serverfunctions.liq | 5 +- 5 files changed, 140 insertions(+), 277 deletions(-) diff --git a/src/engine.liq b/src/engine.liq index 6205b98..4ac9053 100644 --- a/src/engine.liq +++ b/src/engine.liq @@ -83,8 +83,7 @@ output_source = attach_fallback_source(stripped_stream) %include "out_soundcard.liq" # stream output -# FIXME: this needs to be refactored -# %include "out_stream.liq" +%include "out_stream.liq" # enable socket functions %include "serverfunctions.liq" diff --git a/src/library.liq b/src/library.liq index a9ec263..65a4cbe 100644 --- a/src/library.liq +++ b/src/library.liq @@ -38,6 +38,7 @@ end # stream to icecast # ##################### +# FIXME: this function is deprecated and could be removed? def stream_to_icecast( id, encoding, @@ -57,7 +58,6 @@ def stream_to_icecast( channels ) = source = ref(stream) - def on_error(msg) = connected := "false" log(msg) @@ -73,40 +73,25 @@ def stream_to_icecast( user_ref = ref(user) if user == "" then user_ref := "source" end - # TODO Refactor all outgoing stream formats this way - let stereo = (int_of_string(channels) >= 2) - let format = %vorbis(stereo = true) - # let format = %mp3(bitrate = 128, stereo = true) - # FIXME: the format is never overwritten, it is alwas mp3 + snd_icy_metadata = ref(false) + + enc = ref(%vorbis(stereo = true)) - if encoding == "mp3" then%include "outgoing_streams/mp3.liq" end - if encoding == "ogg" then%include "outgoing_streams/ogg.liq" end + # TODO: move this into out_stream.liq? + # if encoding == "ogg" then%include "outgoing_streams/ogg.liq" end + # https://github.com/savonet/liquidsoap/pull/1858 + if + encoding == "mp3" + then + enc := %mp3(stereo = true) + snd_icy_metadata := true + end + # if encoding == "ogg" then%include "outgoing_streams/ogg.liq" end log( - "Icecast output format: #{encoding} #{bitrate} - #{format}" + "Icecast output format: #{encoding} #{bitrate} - #{enc()}" ) - # Liquidsoap cannot handle one output definition for mono and stereo - # FIXME should be working since Liquidsoap 2 - # output_icecast_mono = - # output.icecast( - # id=id, - # host=host, - # port=port, - # password=pass, - # mount=mount_point, - # fallible=true, - # url=url, - # description=description, - # name=name, - # genre=genre, - # user=user_ref(), - # on_error=on_error, - # on_connect=on_connect, - # send_icy_metadata=true, - # format, - # source() - # ) output_icecast_stereo = output.icecast( id=id, @@ -122,8 +107,8 @@ def stream_to_icecast( user=user_ref(), on_error=on_error, on_connect=on_connect, - send_icy_metadata=true, - format, + send_icy_metadata=snd_icy_metadata(), + enc(), source() ) diff --git a/src/out_soundcard.liq b/src/out_soundcard.liq index b210a27..3a19013 100644 --- a/src/out_soundcard.liq +++ b/src/out_soundcard.liq @@ -16,27 +16,18 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -if a0_out != "" then ignore(get_output(output_source, a0_out, "lineout_0")) end - -# if a1_out != "" then -# ignore(get_output(output_source, a1_out, "lineout_1")) -# end - -# if a2_out != "" then -# ignore(get_output(output_source, a2_out, "lineout_2")) -# end - -# if a3_out != "" then -# ignore(get_output(output_source, a3_out, "lineout_3")) -# end - -# if a4_out != "" then -# ignore(get_output(output_source, a4_out, "lineout_4")) - -# #output_4 = ref output.dummy(blank()) -# #get_output(output_4, output_source, a4_out, "lineout_4") - - -# #output_4 := get_output(output_source, a4_out, "lineout_4") -# #get_output(output_source, a4_out, "aura_lineout_4") -# end \ No newline at end of file +playout_count = ref(0) +lo_list = ref([]) + +# create a playout source +# Note: this could be improved by providing more information about +# the device itself via the config file +def create_playout(output_device) = + out = + get_output(output_source, output_device.name, "lineout_#{playout_count()}") + lo_list := [...lo_list(), "linout_#{playout_count()}"] + playout_count := playout_count() + 1 +end + +# iterate over every output unit +list.iter(create_playout, config.audio.devices.output) diff --git a/src/out_stream.liq b/src/out_stream.liq index b8cc253..6cc45d9 100644 --- a/src/out_stream.liq +++ b/src/out_stream.liq @@ -18,96 +18,65 @@ # Output streaming settings # What a mess... -s0_encoding = - get_setting("ogg", "stream_0_encoding", "AURA_ENGINE_STREAM_OUTPUT_ENCODING") -s0_bitrate = - int_of_string( - get_setting("192", "stream_0_bitrate", "AURA_ENGINE_STREAM_OUTPUT_BITRATE") - ) -s0_host = get_setting("", "stream_0_host", "AURA_ENGINE_STREAM_OUTPUT_HOST") -s0_port = - int_of_string( - get_setting("0", "stream_0_port", "AURA_ENGINE_STREAM_OUTPUT_PORT") - ) -s0_user = get_setting("", "stream_0_user", "AURA_ENGINE_STREAM_OUTPUT_USER") -s0_pass = - get_setting("", "stream_0_password", "AURA_ENGINE_STREAM_OUTPUT_PASSWORD") -s0_mount = - get_setting("", "stream_0_mountpoint", "AURA_ENGINE_STREAM_OUTPUT_MOUNTPOINT") -s0_url = get_setting("", "stream_0_url", "AURA_ENGINE_STREAM_OUTPUT_URL") -s0_desc = - get_setting( - "", "stream_0_description", "AURA_ENGINE_STREAM_OUTPUT_DESCRIPTION" - ) -s0_genre = get_setting("", "stream_0_genre", "AURA_ENGINE_STREAM_OUTPUT_GENRE") -s0_name = get_setting("", "stream_0_name", "AURA_ENGINE_STREAM_OUTPUT_NAME") -s0_channels = - get_setting("", "stream_0_channels", "AURA_ENGINE_STREAM_OUTPUT_CHANNELS") - -# s1_encoding = list.assoc(default="", "stream_1_encoding", ini) -# s1_bitrate = int_of_string(list.assoc(default="", "stream_1_bitrate", ini)) -# s1_host = list.assoc(default="", "stream_1_host", ini) -# s1_port = int_of_string(list.assoc(default="", "stream_1_port", ini)) -# s1_user = list.assoc(default="", "stream_1_user", ini) -# s1_pass = list.assoc(default="", "stream_1_password", ini) -# s1_mount = list.assoc(default="", "stream_1_mountpoint", ini) -# s1_url = list.assoc(default="", "stream_1_url", ini) -# s1_desc = list.assoc(default="", "stream_1_description", ini) -# s1_genre = list.assoc(default="", "stream_1_genre", ini) -# s1_name = list.assoc(default="", "stream_1_name", ini) -# s1_channels = list.assoc(default="", "stream_1_channels", ini) - -# s2_encoding = list.assoc(default="", "stream_2_encoding", ini) -# s2_bitrate = int_of_string(list.assoc(default="", "stream_2_bitrate", ini)) -# s2_host = list.assoc(default="", "stream_2_host", ini) -# s2_port = int_of_string(list.assoc(default="", "stream_2_port", ini)) -# s2_user = list.assoc(default="", "stream_2_user", ini) -# s2_pass = list.assoc(default="", "stream_2_password", ini) -# s2_mount = list.assoc(default="", "stream_2_mountpoint", ini) -# s2_url = list.assoc(default="", "stream_2_url", ini) -# s2_desc = list.assoc(default="", "stream_2_description", ini) -# s2_genre = list.assoc(default="", "stream_2_genre", ini) -# s2_name = list.assoc(default="", "stream_2_name", ini) -# s2_channels = list.assoc(default="", "stream_2_channels", ini) - -# s3_encoding = list.assoc(default="", "stream_3_encoding", ini) -# s3_bitrate = int_of_string(list.assoc(default="", "stream_3_bitrate", ini)) -# s3_host = list.assoc(default="", "stream_3_host", ini) -# s3_port = int_of_string(list.assoc(default="", "stream_3_port", ini)) -# s3_user = list.assoc(default="", "stream_3_user", ini) -# s3_pass = list.assoc(default="", "stream_3_password", ini) -# s3_mount = list.assoc(default="", "stream_3_mountpoint", ini) -# s3_url = list.assoc(default="", "stream_3_url", ini) -# s3_desc = list.assoc(default="", "stream_3_description", ini) -# s3_genre = list.assoc(default="", "stream_3_genre", ini) -# s3_name = list.assoc(default="", "stream_3_name", ini) -# s3_channels = list.assoc(default="", "stream_3_channels", ini) +# s0_encoding = +# get_setting("ogg", "stream_0_encoding", "AURA_ENGINE_STREAM_OUTPUT_ENCODING") +# s0_bitrate = +# int_of_string( +# get_setting("192", "stream_0_bitrate", "AURA_ENGINE_STREAM_OUTPUT_BITRATE") +# ) +# s0_host = get_setting("", "stream_0_host", "AURA_ENGINE_STREAM_OUTPUT_HOST") +# s0_port = +# int_of_string( +# get_setting("0", "stream_0_port", "AURA_ENGINE_STREAM_OUTPUT_PORT") +# ) +# s0_user = get_setting("", "stream_0_user", "AURA_ENGINE_STREAM_OUTPUT_USER") +# s0_pass = +# get_setting("", "stream_0_password", "AURA_ENGINE_STREAM_OUTPUT_PASSWORD") +# s0_mount = +# get_setting("", "stream_0_mountpoint", "AURA_ENGINE_STREAM_OUTPUT_MOUNTPOINT") +# s0_url = get_setting("", "stream_0_url", "AURA_ENGINE_STREAM_OUTPUT_URL") +# s0_desc = +# get_setting( +# "", "stream_0_description", "AURA_ENGINE_STREAM_OUTPUT_DESCRIPTION" +# ) +# s0_genre = get_setting("", "stream_0_genre", "AURA_ENGINE_STREAM_OUTPUT_GENRE") +# s0_name = get_setting("", "stream_0_name", "AURA_ENGINE_STREAM_OUTPUT_NAME") +# s0_channels = +# get_setting("", "stream_0_channels", "AURA_ENGINE_STREAM_OUTPUT_CHANNELS") -# s4_encoding = list.assoc(default="", "stream_4_encoding", ini) -# s4_bitrate = int_of_string(list.assoc(default="", "stream_4_bitrate", ini)) -# s4_host = list.assoc(default="", "stream_4_host", ini) -# s4_port = int_of_string(list.assoc(default="", "stream_4_port", ini)) -# s4_user = list.assoc(default="", "stream_4_user", ini) -# s4_pass = list.assoc(default="", "stream_4_password", ini) -# s4_mount = list.assoc(default="", "stream_4_mountpoint", ini) -# s4_url = list.assoc(default="", "stream_4_url", ini) -# s4_desc = list.assoc(default="", "stream_4_description", ini) -# s4_genre = list.assoc(default="", "stream_4_genre", ini) -# s4_name = list.assoc(default="", "stream_4_name", ini) -# s4_channels = list.assoc(default="", "stream_4_channels", ini) +# FIXME: this should move into the list of streams +# What is this used for anyway? +s0_connected = ref("") -s0_connected = ref('') # s1_connected = ref('') # s2_connected = ref('') # s3_connected = ref('') # s4_connected = ref('') -if - s0_enable == true -then - # enable connection status for that stream +# number of streams +stream_count = ref(0) + +# list of streams +stream_list = ref([]) + +def create_stream(stream) = + let url = string(stream.url) + enc = ref(%vorbis(stereo = true)) + + # set icy_metadata accoring to the encoder (enc) to send + # metadata via icecast + snd_icy_metadata = ref(false) + if + stream.encoding == "mp3" + then + enc := %mp3(stereo = true) + snd_icy_metadata := true + end + + # register a server function for every stream + # TODO: what is this used for? server.register( - namespace="out_http_0", + namespace="out_http_#{stream_count()}", "connected", fun (s) -> begin @@ -116,152 +85,74 @@ then end ) - # aaand stream - stream_to_icecast( - "out_http_0", - s0_encoding, - s0_bitrate, - s0_host, - s0_port, - s0_pass, - s0_mount, - s0_url, - s0_desc, - s0_genre, - s0_user, - output_source, - "0", - s0_connected, - s0_name, - s0_channels + # create a list of streams to keep track of the created streams + # if + # list.assoc.mem(url, stream_list()) + # then + # "Stream for url #{url} already exists!" + # else + out_stream = + output.icecast( + id="out_http_#{stream_count()}", + host=stream.host, + port=int_of_float(stream.port), + password=stream.password, + mount=stream.mountpoint, + fallible=true, + url=stream.url, + description=stream.description, + name=stream.name, + genre=stream.genre, + user=stream.user, + send_icy_metadata=snd_icy_metadata(), + enc(), + output_source + ) + stream_count := stream_count() + 1 + + # append the new stream to the list of streams, the key is the url + # if we could alter the unit (stream) with all its values we could + # save all of this in the stream unit itself + stream_list := [...stream_list(), (url, out_stream.shutdown)] + + print( + "Stream: #{url}" ) end -# if -# s1_enable == true -# then +# def create_stream(stream) = # server.register( -# namespace="out_http_1", +# namespace="out_http_#{stream_count()}", # "connected", # fun (s) -> # begin # ignore(s) -# s1_connected() +# s0_connected() # end # ) # stream_to_icecast( -# "out_http_1", -# s1_encoding, -# s1_bitrate, -# s1_host, -# s1_port, -# s1_pass, -# s1_mount, -# s1_url, -# s1_desc, -# s1_genre, -# s1_user, +# "out_http_#{stream_count()}", +# stream.encoding, +# int_of_string(stream.bitrate), +# stream.host, +# int_of_float(stream.port), +# stream.password, +# stream.mount, +# stream.url, +# stream.desc, +# stream.genre, +# stream.user, # output_source, -# "1", -# s1_connected, -# s1_name, -# s1_channels +# "#{stream_count()}", +# s0_connected, +# stream.name, +# stream.channels # ) -# end - -# if -# s2_enable == true -# then -# server.register( -# namespace="out_http_2", -# "connected", -# fun (s) -> -# begin -# ignore(s) -# s2_connected() -# end -# ) -# stream_to_icecast( -# "out_http_2", -# s2_encoding, -# s2_bitrate, -# s2_host, -# s2_port, -# s2_pass, -# s2_mount, -# s2_url, -# s2_desc, -# s2_genre, -# s2_user, -# output_source, -# "2", -# s2_connected, -# s2_name, -# s2_channels -# ) -# end - -# if -# s3_enable == true -# then -# server.register( -# namespace="out_http_3", -# "connected", -# fun (s) -> -# begin -# ignore(s) -# s3_connected() -# end -# ) -# stream_to_icecast( -# "out_http_3", -# s3_encoding, -# s3_bitrate, -# s3_host, -# s3_port, -# s3_pass, -# s3_mount, -# s3_url, -# s3_desc, -# s3_genre, -# s3_user, -# output_source, -# "3", -# s3_connected, -# s3_name, -# s3_channels +# stream_count := stream_count() + 1 +# print( +# "Registered stream #{stream.url}" # ) # end - -# if -# s4_enable == true -# then -# server.register( -# namespace="out_http_4", -# "connected", -# fun (s) -> -# begin -# ignore(s) -# s4_connected() -# end -# ) -# stream_to_icecast( -# "out_http_4", -# s4_encoding, -# s4_bitrate, -# s4_host, -# s4_port, -# s4_pass, -# s4_mount, -# s4_url, -# s4_desc, -# s4_genre, -# s4_user, -# output_source, -# "4", -# s4_connected, -# s4_name, -# s4_channels -# ) -# end \ No newline at end of file +# itterate over all stream units (read from the yaml config) +list.iter(create_stream, config.stream) diff --git a/src/serverfunctions.liq b/src/serverfunctions.liq index d235f89..24b269f 100644 --- a/src/serverfunctions.liq +++ b/src/serverfunctions.liq @@ -134,15 +134,12 @@ server.register( # s4_enable # ? list.add(("out_line_4", ("connected", "#{s4_connected()}")), so) : so - lo = [] - lo = a0_out != '' ? list.add("out_line_0", lo) : lo - # lo = a1_out != '' ? list.add("out_line_1", lo) : lo # lo = a2_out != '' ? list.add("out_line_2", lo) : lo # lo = a3_out != '' ? list.add("out_line_3", lo) : lo # lo = a4_out != '' ? list.add("out_line_4", lo) : lo json_data = json() - json_data.add("line", lo) + json_data.add("line", lo_list()) json_data.add("stream", so) json.stringify(json_data) end -- GitLab