123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- def notify(m)
- command = "timeout --signal=KILL 45 pyponotify --media-id=#{m['schedule_table_id']} &"
- log(command)
- system(command)
- end
- def notify_queue(m)
- f = !dynamic_metadata_callback
- ignore(f(m))
- notify(m)
- end
- def notify_stream(m)
- json_str = string.replace(pattern="\n",(fun (s) -> ""), json_of(m))
- #if a string has a single apostrophe in it, let's comment it out by ending the string before right before it
- #escaping the apostrophe, and then starting a new string right after it. This is why we use 3 apostrophes.
- json_str = string.replace(pattern="'",(fun (s) -> "'\''"), json_str)
- command = "timeout --signal=KILL 45 pyponotify --webstream='#{json_str}' --media-id=#{!current_dyn_id} &"
-
- if !current_dyn_id != "-1" then
- log(command)
- system(command)
- end
- end
- # A function applied to each metadata chunk
- def append_title(m) =
- log("Using stream_format #{!stream_metadata_type}")
- if list.mem_assoc("mapped", m) then
- #protection against applying this function twice. It shouldn't be happening
- #and bug file with Liquidsoap.
- m
- else
- if !stream_metadata_type == 1 then
- [("title", "#{!show_name} - #{m['artist']} - #{m['title']}"), ("mapped", "true")]
- elsif !stream_metadata_type == 2 then
- [("title", "#{!station_name} - #{!show_name}"), ("mapped", "true")]
- else
- [("title", "#{m['artist']} - #{m['title']}"), ("mapped", "true")]
- end
- end
- end
- def crossfade_airtime(s)
- #duration is automatically overwritten by metadata fields passed in
- #with audio
- s = fade.in(type="log", duration=0., s)
- s = fade.out(type="log", duration=0., s)
- fader = fun (a,b) -> add(normalize=false,[b,a])
- cross(fader,s)
- end
- def transition(a,b) =
- log("transition called...")
- add(normalize=false,
- [ sequence([ blank(duration=0.01),
- fade.initial(duration=!default_dj_fade, b) ]),
- fade.final(duration=!default_dj_fade, a) ])
- end
- # we need this function for special transition case(from default to queue)
- # we don't want the trasition fade to have effect on the first song that would
- # be played siwtching out of the default(silent) source
- def transition_default(a,b) =
- log("transition called...")
- if !just_switched then
- just_switched := false
- add(normalize=false,
- [ sequence([ blank(duration=0.01),
- fade.initial(duration=!default_dj_fade, b) ]),
- fade.final(duration=!default_dj_fade, a) ])
- else
- just_switched := false
- b
- end
- end
- # Define a transition that fades out the
- # old source, adds a single, and then
- # plays the new source
- def to_live(old,new) =
- # Fade out old source
- old = fade.final(old)
- # Compose this in sequence with
- # the new source
- sequence([old,new])
- end
- def output_to(output_type, type, bitrate, host, port, pass, mount_point, url, description, genre, user, s, stream, connected, name, channels) =
- source = ref s
- def on_error(msg)
- connected := "false"
- command = "timeout --signal=KILL 45 pyponotify --error='#{msg}' --stream-id=#{stream} --time=#{!time} &"
- system(command)
- log(command)
- 5.
- end
- def on_connect()
- connected := "true"
- command = "timeout --signal=KILL 45 pyponotify --connect --stream-id=#{stream} --time=#{!time} &"
- system(command)
- log(command)
- end
- stereo = (channels == "stereo")
- if output_type == "icecast" then
- user_ref = ref user
- if user == "" then
- user_ref := "source"
- end
- output_mono = output.icecast(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)
- output_stereo = output.icecast(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)
- if type == "mp3" then
- %include "mp3.liq"
- end
- if type == "ogg" then
- %include "ogg.liq"
- end
- %ifencoder %opus
- if type == "opus" then
- %include "opus.liq"
- end
- %endif
- %ifencoder %aac
- if type == "aac" then
- %include "aac.liq"
- end
- %endif
- %ifencoder %aacplus
- if type == "aacplus" then
- %include "aacplus.liq"
- end
- %endif
- %ifencoder %fdkaac
- if type == "fdkaac" then
- %include "fdkaac.liq"
- end
- %endif
- else
- user_ref = ref user
- if user == "" then
- user_ref := "source"
- end
- output_mono = output.shoutcast(id = "shoutcast_stream_#{stream}",
- host = host,
- port = port,
- password = pass,
- fallible = true,
- url = url,
- genre = genre,
- name = description,
- user = !user_ref,
- on_error = on_error,
- on_connect = on_connect)
- output_stereo = output.shoutcast(id = "shoutcast_stream_#{stream}",
- host = host,
- port = port,
- password = pass,
- fallible = true,
- url = url,
- genre = genre,
- name = description,
- user = !user_ref,
- on_error = on_error,
- on_connect = on_connect)
- if type == "mp3" then
- %include "mp3.liq"
- end
- %ifencoder %aac
- if type == "aac" then
- %include "aac.liq"
- end
- %endif
-
- %ifencoder %aacplus
- if type == "aacplus" then
- %include "aacplus.liq"
- end
- %endif
- end
- end
- # Add a skip function to a source
- # when it does not have one
- # by default
- #def add_skip_command(s)
- # # A command to skip
- # def skip(_)
- # # get playing (active) queue and flush it
- # l = list.hd(server.execute("queue.secondary_queue"))
- # l = string.split(separator=" ",l)
- # list.iter(fun (rid) -> ignore(server.execute("queue.remove #{rid}")), l)
- #
- # l = list.hd(server.execute("queue.primary_queue"))
- # l = string.split(separator=" ", l)
- # if list.length(l) > 0 then
- # source.skip(s)
- # "Skipped"
- # else
- # "Not skipped"
- # end
- # end
- # # Register the command:
- # server.register(namespace="source",
- # usage="skip",
- # description="Skip the current song.",
- # "skip",fun(s) -> begin log("source.skip") skip(s) end)
- #end
- def clear_queue(s)
- source.skip(s)
- end
- def set_dynamic_source_id(id) =
- current_dyn_id := id
- string_of(!current_dyn_id)
- end
- def get_dynamic_source_id() =
- string_of(!current_dyn_id)
- end
- #cc-4633
- # NOTE
- # A few values are hardcoded and may be dependent:
- # - the delay in gracetime is linked with the buffer duration of input.http
- # (delay should be a bit less than buffer)
- # - crossing duration should be less than buffer length
- # (at best, a higher duration will be ineffective)
- # HTTP input with "restart" command that waits for "stop" to be effected
- # before "start" command is issued. Optionally it takes a new URL to play,
- # which makes it a convenient replacement for "url".
- # In the future, this may become a core feature of the HTTP input.
- # TODO If we stop and restart quickly several times in a row,
- # the data bursts accumulate and create buffer overflow.
- # Flushing the buffer on restart could be a good idea, but
- # it would also create an interruptions while the buffer is
- # refilling... on the other hand, this would avoid having to
- # fade using both cross() and switch().
- def input.http_restart(~id,~initial_url="http://dummy/url")
- source = audio_to_stereo(input.http(buffer=5.,max=15.,id=id,autostart=false,initial_url))
- def stopped()
- "stopped" == list.hd(server.execute("#{id}.status"))
- end
- server.register(namespace=id,
- "restart",
- usage="restart [url]",
- fun (url) -> begin
- if url != "" then
- log(string_of(server.execute("#{id}.url #{url}")))
- end
- log(string_of(server.execute("#{id}.stop")))
- add_timeout(0.5,
- { if stopped() then
- log(string_of(server.execute("#{id}.start"))) ;
- (-1.)
- else 0.5 end})
- "OK"
- end)
- # Dummy output should be useless if HTTP stream is meant
- # to be listened to immediately. Otherwise, apply it.
- #
- # output.dummy(fallible=true,source)
- source
- end
- # Transitions between URL changes in HTTP streams.
- def cross_http(~debug=true,~http_input_id,source)
- id = http_input_id
- last_url = ref ""
- change = ref false
- def on_m(m)
- notify_stream(m)
- changed = m["source_url"] != !last_url
- log("URL now #{m['source_url']} (change: #{changed})")
- if changed then
- if !last_url != "" then change := true end
- last_url := m["source_url"]
- end
- end
- # We use both metadata and status to know about the current URL.
- # Using only metadata may be more precise is crazy corner cases,
- # but it's also asking too much: the metadata may not pass through
- # before the crosser is instantiated.
- # Using only status in crosser misses some info, eg. on first URL.
- source = on_metadata(on_m,source)
- cross_d = 3.
- def crosser(a,b)
- url = list.hd(server.execute('#{id}.url'))
- status = list.hd(server.execute('#{id}.status'))
- on_m([("source_url",url)])
- if debug then
- log("New track inside HTTP stream")
- log(" status: #{status}")
- log(" need to cross: #{!change}")
- log(" remaining #{source.remaining(a)} sec before, \
- #{source.remaining(b)} sec after")
- end
- if !change then
- change := false
- # In principle one should avoid crossing on a live stream
- # it'd be okay to do it here (eg. use add instead of sequence)
- # because it's only once per URL, but be cautious.
- sequence([fade.out(duration=cross_d,a),fade.in(b)])
- else
- # This is done on tracks inside a single stream.
- # Do NOT cross here or you'll gradually empty the buffer!
- sequence([a,b])
- end
- end
- # Setting conservative=true would mess with the delayed switch below
- cross(duration=cross_d,conservative=false,crosser,source)
- end
- # Custom fallback between http and default source with fading of
- # beginning and end of HTTP stream.
- # It does not take potential URL changes into account, as long as
- # they do not interrupt streaming (thanks to the HTTP buffer).
- def http_fallback(~http_input_id,~http,~default)
- id = http_input_id
- # We use a custom switching predicate to trigger switching (and thus,
- # transitions) before the end of a track (rather, end of HTTP stream).
- # It is complexified because we don't want to trigger switching when
- # HTTP disconnects for just an instant, when changing URL: for that
- # we use gracetime below.
- def gracetime(~delay=3.,f)
- last_true = ref 0.
- { if f() then
- last_true := gettimeofday()
- true
- else
- gettimeofday() < !last_true+delay
- end }
- end
- def connected()
- status = list.hd(server.execute("#{id}.status"))
- not(list.mem(status,["polling","stopped"]))
- end
- connected = gracetime(connected)
- def to_live(a,b) =
- log("TRANSITION to live")
- add(normalize=false,
- [fade.initial(b),fade.final(a)])
- end
- def to_static(a,b) =
- log("TRANSITION to static")
- sequence([fade.out(a),fade.initial(b)])
- end
- switch(
- track_sensitive=false,
- transitions=[to_live,to_static],
- [(# make sure it is connected, and not buffering
- {connected() and source.is_ready(http) and !webstream_enabled}, http),
- ({true},default)])
- end
|