AzuraCast
AzuraCast copied to clipboard
Start/end dates not respected by Liquidsoap playlists
Using Docker installation method Yes
Host Operating System Ubuntu 18.04 (LTS), AzuraCast v0.9.9, #ef6393e (2020-03-22 22:40)
Describe the bug The start/end dates are not respected by Liquidsoap playlists when they have start/end dates and a day of the week configured.
To Reproduce Steps to reproduce the behavior:
- Create a new playlist with the following settings :
-
Interrupt other songs to play at scheduled time.
,Only loop through playlist once.
andMerge playlist to play as a single track.
- add a schedule with a start and end time, a start and end date, and check one day of the week.
- Restart the broadcasting to apply the settings
The playlist will play on the specified day of the week event though the end date is already passed
Expected behavior The playlist is not played when we're outside the scheduled period.
Relevant Logs There's nothing really interesting in the logs, but the generated Liquidsoap configuration does not have any information about the start and en date (which is not surprising because it's not supported)
Here's the Liquidsoap config, generated on a day outside the start/end dates :
# WARNING! This file is automatically generated by AzuraCast.
# Do not update it directly!
set("init.daemon", false)
set("init.daemon.pidfile.path","/var/azuracast/stations/ebull_radio/config/liquidsoap.pid")
set("log.stdout", true)
set("log.file", false)
set("server.telnet",true)
set("server.telnet.bind_addr","0.0.0.0")
set("server.telnet.port", 8004)
set("harbor.bind_addrs",["0.0.0.0"])
set("tag.encodings",["UTF-8","ISO-8859-1"])
set("encoder.encoder.export",["artist","title","album","song"])
setenv("TZ", "Europe/Zurich")
playlist_fete_au_balcon = playlist.once(id="playlist_fete_au_balcon",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_fete_au_balcon.m3u")
playlist_fete_au_balcon = audio_to_stereo(id="stereo_playlist_fete_au_balcon", playlist_fete_au_balcon)
playlist_fete_au_balcon = cue_cut(id="cue_playlist_fete_au_balcon", playlist_fete_au_balcon)
playlist_tp2 = playlist(id="playlist_tp2",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_tp2.m3u")
playlist_tp2 = audio_to_stereo(id="stereo_playlist_tp2", playlist_tp2)
playlist_tp2 = cue_cut(id="cue_playlist_tp2", playlist_tp2)
playlist_tp3 = playlist.once(id="playlist_tp3",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_tp3.m3u")
playlist_tp3 = audio_to_stereo(id="stereo_playlist_tp3", playlist_tp3)
playlist_tp3 = cue_cut(id="cue_playlist_tp3", playlist_tp3)
playlist_chill_tony_jay = playlist(id="playlist_chill_tony_jay",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_chill_tony_jay.m3u")
playlist_chill_tony_jay = audio_to_stereo(id="stereo_playlist_chill_tony_jay", playlist_chill_tony_jay)
playlist_chill_tony_jay = cue_cut(id="cue_playlist_chill_tony_jay", playlist_chill_tony_jay)
playlist_tp4 = playlist.once(id="playlist_tp4",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_tp4.m3u")
playlist_tp4 = audio_to_stereo(id="stereo_playlist_tp4", playlist_tp4)
playlist_tp4 = cue_cut(id="cue_playlist_tp4", playlist_tp4)
playlist_general = playlist(id="playlist_general",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_general.m3u")
playlist_general = audio_to_stereo(id="stereo_playlist_general", playlist_general)
playlist_general = cue_cut(id="cue_playlist_general", playlist_general)
playlist_jingles = playlist(id="playlist_jingles",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_jingles.m3u")
playlist_jingles = audio_to_stereo(id="stereo_playlist_jingles", playlist_jingles)
playlist_jingles = cue_cut(id="cue_playlist_jingles", playlist_jingles)
playlist_jingles = drop_metadata(playlist_jingles)
# Standard Playlists
radio = random(id="ebull_radio_standard_playlists", weights=[3], [playlist_general])
# Standard Schedule Switches
radio = switch(id="ebull_radio_schedule_switch", track_sensitive=true, [ ({ (5w) and 18h0m-20h0m }, playlist_fete_au_balcon), ({ (0w) and 16h0m-18h0m }, playlist_tp2), ({ (1w) and 14h15m-15h20m }, playlist_chill_tony_jay), ({ (4w) and 16h0m-18h0m }, playlist_tp4), ({true}, radio) ])
# Once per x Songs Playlists
radio = rotate(weights=[1,8], [playlist_jingles, radio])
# AutoDJ Next Song Script
def azuracast_next_song() =
uri = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/nextsong --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
log("AzuraCast Raw Response: #{uri}")
if uri == "" or string.match(pattern="Error", uri) then
log("AzuraCast Error: Delaying subsequent requests...")
system("sleep 2")
request.create("")
else
request.create(uri)
end
end
dynamic = request.dynamic(id="ebull_radio_next_song", timeout=20., azuracast_next_song)
dynamic = audio_to_stereo(id="ebull_radio_stereo_next_song", dynamic)
dynamic = cue_cut(id="ebull_radio_cue_next_song", dynamic)
radio = fallback(id="ebull_radio_autodj_fallback", track_sensitive = true, [dynamic, radio])
# Interrupting Schedule Switches
radio = switch(id="ebull_radio_interrupt_switch", track_sensitive=false, [ ({ (2w) and 16h0m-18h0m }, playlist_tp3), ({true}, radio) ])
requests = request.queue(id="ebull_radio_requests")
requests = audio_to_stereo(id="ebull_radio_stereo_requests", requests)
requests = cue_cut(id="ebull_radio_cue_requests", requests)
radio = fallback(id="ebull_radio_requests_fallback", track_sensitive = true, [requests, radio])
add_skip_command(radio)
radio = fallback(id="ebull_radio_safe_fallback", track_sensitive = false, [radio, single(id="error_jingle", "/usr/local/share/icecast/web/error.mp3")])
radio = crossfade(smart=true, duration=3.,fade_out=2.,fade_in=2.,radio)
# DJ Authentication
live_enabled = ref false
last_authenticated_dj = ref ""
live_dj = ref ""
def dj_auth(auth_user,auth_pw) =
user = ref ""
password = ref ""
if (auth_user == "source" or auth_user == "") and (string.match(pattern="(:|,)+", auth_pw)) then
auth_string = string.split(separator="(:|,)", auth_pw)
user := list.nth(default="", auth_string, 0)
password := list.nth(default="", auth_string, 2)
else
user := auth_user
password := auth_pw
end
log("Authenticating DJ: #{!user}")
ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/auth --form dj-user="^string.quote(!user)^" --form dj-password="^string.quote(!password)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
log("AzuraCast DJ Auth Response: #{ret}")
authed = bool_of_string(ret)
if (authed) then
last_authenticated_dj := !user
end
authed
end
def live_connected(header) =
dj = !last_authenticated_dj
log("DJ Source connected! Last authenticated DJ: #{dj} - #{header}")
live_enabled := true
live_dj := dj
ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/djon --form dj-user="^string.quote(dj)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
log("AzuraCast Live Connected Response: #{ret}")
end
def live_disconnected() =
dj = !live_dj
log("DJ Source disconnected! Current live DJ: #{dj}")
ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/djoff --form dj-user="^string.quote(dj)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
log("AzuraCast Live Disconnected Response: #{ret}")
live_enabled := false
last_authenticated_dj := ""
live_dj := ""
end
# A Pre-DJ source of radio that can be broadcast if needed
radio_without_live = radio
ignore(radio_without_live)
# Live Broadcasting
live = audio_to_stereo(input.harbor("/", id = "ebull_radio_input_streamer", port = 8005, auth = dj_auth, icy = true, icy_metadata_charset = "UTF-8", metadata_charset = "UTF-8", on_connect = live_connected, on_disconnect = live_disconnected, buffer = 5., max = 10.))
ignore(output.dummy(live, fallible=true))
radio = fallback(id="ebull_radio_live_fallback", track_sensitive=false, [live, radio])
# Record Live Broadcasts
stop_recording_f = ref (fun () -> ())
def start_recording(path) =
output_live_recording = output.file(%mp3(samplerate=44100, stereo=true, bitrate=128, id3v2=true), fallible=true, reopen_on_metadata=false, "#{path}", live)
stop_recording_f := fun () -> source.shutdown(output_live_recording)
end
def stop_recording() =
f = !stop_recording_f
f ()
stop_recording_f := fun () -> ()
end
server.register(namespace="recording", description="Start recording.", usage="recording.start filename", "start", fun (s) -> begin start_recording(s) "Done!" end)
server.register(namespace="recording", description="Stop recording.", usage="recording.stop", "stop", fun (s) -> begin stop_recording() "Done!" end)
# Allow for Telnet-driven insertion of custom metadata.
radio = server.insert_metadata(id="custom_metadata", radio)
# Apply amplification metadata (if supplied)
radio = amplify(override="liq_amplify", 1., radio)
# Normalization and Compression
radio = normalize(target = 0., window = 0.03, gain_min = -16., gain_max = 0., radio)
radio = compress.exponential(radio, mu = 1.0)
# Send metadata changes back to AzuraCast
def metadata_updated(m) =
if (m["song_id"] != "") then
ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/feedback --form song="^string.quote(m["song_id"])^" --form media="^string.quote(m["media_id"])^" --form playlist="^string.quote(m["playlist_id"])^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
log("AzuraCast Feedback Response: #{ret}")
end
end
radio = on_metadata(metadata_updated,radio)
# Local Broadcasts
output.icecast(%mp3(samplerate=44100, stereo=true, bitrate=128, id3v2=true), id="ebull_radio_local_1", host = "127.0.0.1", port = 8000, password = "yz34tKCC", mount = "/radio.mp3", name = "Ebull Radio", description = "", genre = "", url = "http://www.ebull.ch", public = false, encoding = "UTF-8", radio)
# Remote Relays
playlist_tp2
has an end date that's already passed, but the it's still playing on the specified day of the week.
Device(s): All platforms
Additional context I have a cron restarting the broadcasting very day, so refreshing Liquidsoap is not the issue I think.
A workaround is simply to disable the playlist and restart broadcasting, but it's 1) manual and 2) needs a brodcast restart, wich is not great.
@loics2 This is correct; if you're using the AzuraCast AutoDJ, it can properly keep the playlist only playing within the specified date range, but if you use Liquidsoap in manual mode (as you do when you have the advanced AutoDJ settings turned on), it depends on Liquidsoap's configuration to do this, and currently, the Liquidsoap configuration doesn't limit by a start and end date.
Honestly, the reason for this is because I simply don't know how to accomplish that in the Liquidsoap scripting language, and I couldn't find any documentation online about how to do it. I wanted to roll out the feature for the AzuraCast AutoDJ users (the majority of users) but meant to follow up with the LS team to ask them how (if it's at all possible) to actually implement a start and end date boundary for switches in Liquidsoap configuration, but I haven't gotten around to it yet.
An easy solution could just be to remove the playlist from the generated Liquidsoap config. It would still need frequent broadcast restart, but still better than nothing. I don't really know how Liquidsoap work for now, so I don't know if there's another solution.
I'm slowly digging into the source code of Azuracast, so I could try to implement this simple solution if you're interested.
@loics2 I wouldn't necessarily count that as an actual solution. Really I just need to ask the Liquidsoap folks about how to accomplish this, and then draw conclusions based on that.
Just for my own documentation purposes, the response from the LS Slack was:
you may need to write your own function to act as a predicate
def checkDate() ... end
switch([{checkDate()}, source), ...])
convert your date range to unix seconds and usegettimeofday()
It appears there's no out-of-the-box way of doing this, so we'll have to add the ability to write that kind of function to our configuration generator. We'll get around to it as soon as we can. In the meantime, we may simply remove those specific schedule entries.
Start/end dates and hours not working if you don't touch to advanced settings or activate manual auto dj mode. We are experiencing difficulties with playlist schedulings too.
@radiolize This issue is not related to whether scheduled times operate correctly with the AzuraCast AutoDJ, this is specifically an upstream issue with the Liquidsoap scripting that we're working on separately.
For issues with the scheduling issue, please see #2631, and please supply ample documentation of your playlists, their settings, and what the station playback queue is showing.