Skip to content

Instantly share code, notes, and snippets.

@gilou
Created May 24, 2021 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gilou/45f636c04e7a74d5a01946a49167363e to your computer and use it in GitHub Desktop.
Save gilou/45f636c04e7a74d5a01946a49167363e to your computer and use it in GitHub Desktop.
Liquidsoap function to avoid repetitions of tracks based on the filename
# Compare two pairs, according to snd value
def compare_snd(a, b)
if snd(a) == snd(b) then
0
else
if snd(a) > snd(b) then
1
else
-1
end
end
end
# Returns pairs where snd is more than a given value
def more_than(p, ~value)
snd(p) > value
end
def log_track(m, ~played_recently)
now = gettimeofday()
log(level=3, 'Playing (#{now}): ' ^ m['initial_uri'])
played_recently := list.add((m['initial_uri'], now), !played_recently)
log(level=4, 'I have ' ^ string_of(list.length(!played_recently)) ^ " tracks")
end
# Function to be used as check_next for playlist*()
# uses a list to track played files, and returns false if the timestamp
# is too recent, unless it failed to select a track more than 3 times for a
# given source
# @param ~played_recently list of (played URIs, timestamp), optionnaly shared amongst multiple sources
# @param ~playlist_hunger list of (source, number of failures) to keep track of failed selections and avoid infinite loops
# @param ~delay_repeat how long between two repetitions of a file
# @param ~allowed_failures how many failures before giving up (can be high depending on the scenario)
# @param req request sent from check_next
def no_repeat(
~played_recently = ref [("empty", 0.0)],
~playlist_hunger = ref [("empty", 0)],
~delay_repeat = 30.0*60.0,
~allowed_failures = 200,
~notify_hunger = fun(src, track) -> (),
req
)
# This will be our final decision, do we play or skip the file?
do_play = ref true
# This tells us wether we played because we already skipped too many
meta = request.metadata(req)
name = meta['initial_uri']
source = meta['source']
# Gather all previously played information for the file
last_times = list.filter_assoc(name, !played_recently)
log(level=4,"checking "^ string_of(list.length(!played_recently)) ^ " entries for " ^ name)
# No result, good to go
if list.length(last_times) == 0 then
log(name ^ " was never played, not skipping")
else
# Sort descending by play time
last_times = list.rev(list.sort(compare_snd, last_times))
log(level=4,"track played "^ string_of(list.length(last_times)) ^ " times")
# Last time is the first pair of the list
last_time = snd(list.hd(default=("", 0.0), last_times))
if last_time < gettimeofday() - delay_repeat then
log(level=4,"last played at "^ string_of(last_time) ^", playing")
else
log(level=4,"track played in the last period")
nb_errors = list.hd(default=("", 0), list.filter_assoc(source, !playlist_hunger))
log(level=4,"we already have #{string_of(snd(nb_errors))} failures for #{source}")
if fst(nb_errors) == "" then
log(level=4,"hunger not initialized for #{source}, setting to 1")
playlist_hunger := list.add((source, 1), !playlist_hunger)
do_play := false
else
log(level=4,"signaling a new hunger for #{source}, was set to " ^ string_of(snd(nb_errors)))
playlist_hunger := list.remove_assoc(source, !playlist_hunger)
playlist_hunger := list.add((fst(nb_errors), snd(nb_errors)+1), !playlist_hunger)
if snd(nb_errors) > allowed_failures then
notify_hunger(source, name)
log("Source #{source} failed more than #{allowed_failures} times, we repeat #{name} !")
else
log("Skipping track #{name}: played #{string_of(last_time)}")
do_play := false
end
end
end
end
# Remove old entries
size = list.length(!played_recently)
old = gettimeofday() - delay_repeat*2.0
played_recently := list.filter(more_than(value=old), !played_recently)
deleted = size - list.length(!played_recently)
log(level=4, "cleaned #{deleted} entries")
# if we play, reset hunger counters
if !do_play then
log(level=4, "resetting hunger counter for #{source}")
playlist_hunger := list.remove_assoc(source, !playlist_hunger)
playlist_hunger := list.add((source, 0), !playlist_hunger)
end
!do_play
end
# Store pairs (filename, timestamp) of played files
played_recently = ref [("empty", 0.0)]
# Remember every time we skipped a given source to avoid an infinite loop
playlist_hunger = ref [("empty", 0)]
radio = playlist("mylist.m3u", check_next=no_repeat(played_recently=played_recently, playlist_hunger=playlist_hunger)
radio = on_track(log_track(played_recently=played_recently), radio)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment