savonet / liquidsoap

Liquidsoap is a statically typed scripting general-purpose language with dedicated operators and backend for all thing media, streaming, file generation, automation, HTTP backend and more.
http://liquidsoap.info
GNU General Public License v2.0
1.4k stars 130 forks source link

Harbor: multiple sources for same mount #864

Closed shizmob closed 5 years ago

shizmob commented 5 years ago

Hi! First of all, thank you for writing Liquidsoap, I've been investigating it for usage in my own radio and it seems very useful and thorough.

One feature I'm looking to implement is Icecast proxying/multiplexing: multiple people can connect to the same mount, and it will only relay the (meta)data for one of them according to some decision process (first-connected in my case). This is to facilitate hand-off between DJs and between a DJ and the AFK streamer.

I was looking through the documentation and source code, and it looked like to me that Harbor is hardcoded to only accept a single input per mount point. Is this assessment true, and/or could what I'm looking for be implemented in some other way?

toots commented 5 years ago

Hi,

Thanks for the nice words!

If I understand what you are doing well, then the right way to implement this in liquidsoap would be to assign each DJ their own harbor input and to decide later which of their stream is played, for instance in a switch or a fallback. Here's a quick example with 2 DJs:

dj1 = input.harbor("dj1")
dj2 = input.harbor("dj2")

live_source = fallback(track_sensitive=false,[dj1,dj2])

Hope that helps!

I'll close this here but feel free to continue the conversation in the issue if you need more details.

shizmob commented 5 years ago

Hi, thanks! I've considered that solution, but the main problem is that it will always prioritize dj1 over dj2 if I'm understanding it right, while I'd like to prioritize whoever connected first (DJs trying to mess with eachother does happen, and it'd be bad if one could just intrude in another one's stream simply by virtue of being earlier in the list).

smimram commented 5 years ago

I guess it should be doable to add that behavior to switch.

toots commented 5 years ago

You can code it:

is_connected = ref ""

def on_connect(name,_) =
  if !is_connected == "" then
    is_connected := name
  end
end

# Partial application limitation here..
def on_disconnect(name) =
  def fn() =
    if !is_connected == name then
      is_connected := ""
    end
  end
  fn
end

dj1 = input.harbor(on_connect=on_connect("dj1"),
                   on_disconnect=on_disconnect("dj1"),
                   "dj1")

dj2 = input.harbor(on_connect=on_connect("dj2"),
                   on_disconnect=on_disconnect("dj2"),
                   "dj2")

s = switch(track_sensitive=false, [
   ({!is_connected == "dj1"},dj1),
   ({!is_connected == "dj2"},dj2)
])
shizmob commented 5 years ago

Doesn't that essentially break if dj2 connects before dj1 disconnects, since dj1's on_disconnect call will reset it to ""? I guess a list could be used instead...

toots commented 5 years ago

Doesn't that essentially break if dj2 connects before dj1 disconnects, since dj1's on_disconnect call will reset it to ""? I guess a list could be used instead...

Ha true! Let's try another way:

connected = ref []

def on_connect(name,_) =
  if not list.mem(name,!connected) then
    connected := list.rev(list.add(name,list.rev(!connected)))
  end
end

# Partial application limitation here..
def on_disconnect(name) =
  def fn() =
    list.remove(name,!connected)
  end
  fn
end

dj1 = input.harbor(on_connect=on_connect("dj1"),
                   on_disconnect=on_disconnect("dj1"),
                   "dj1")

dj2 = input.harbor(on_connect=on_connect("dj2"),
                   on_disconnect=on_disconnect("dj2"),
                   "dj2")

s = switch(track_sensitive=false, [
   ({list.hd(default="",!connected) == "dj1"},dj1),
   ({list.hd(default="",!connected) == "dj2"},dj2)
])

I haven't tested the code but that should give you an idea.

The general philosophy we're trying to stick with for liquidsoap is to implement simple, elementary operators and build complex cases from them. As @dbaelde once quoted from someone else (?): "simple things should be simple but complex things should be possible".. 🙂