Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Question: Skipping to Next Track After DJ Disconnects #2107

Closed
BusterNeece opened this issue Dec 14, 2021 · 7 comments
Closed

Question: Skipping to Next Track After DJ Disconnects #2107

BusterNeece opened this issue Dec 14, 2021 · 7 comments
Labels

Comments

@BusterNeece
Copy link

The previous issue on this got closed, and unfortunately that was the end of the follow-up that we heard about it, but it's a huge issue that's been a big deal on our users' minds for many years now, so I'm determined to get to the bottom of it, even if it means being a little pesky on here!

Basically, here's the solution we've come up with that is an almost-perfect way of skipping to the next track after a live DJ disconnects from the stream:

        def live_in(a,b)
            sequence([a, b])
        end
        
        def live_out(a,b)
            faded = sequence([b, a])
            thread.run.recurrent(delay=10., { source.skip(a) ; -1.})
            faded
        end
        
        radio = fallback(id="live_fallback", replay_metadata=false, track_sensitive=false, transitions=[live_out, live_in], [live, radio])

There's just one problem: if a user has crossfading enabled, you hear the live broadcast abruptly disconnect, then the previously playing track plays, but is immediately faded into the next track. It's the auditory equivalent of this:

L=Live
A=Previous track
B=New track

AAAAAAAALLLLLLLLLLLLLLLLAaaabbbBBBB

Basically...I need to be able to disable crossfading, but for that skip only, then return to normal fading rules afterward.

Things I've tried:

  • Setting a minimum on the crossfade to 0., or 1., or any of a number of higher values
  • Turning conservative=false on the crossfade to prevent it from "buffering" any amount of the previous track
  • Calling source.skip on the source from before the crossfade

No dice on any of them.

I feel like we're 95% of the way to having a great solution here that works for our users. For users with crossfading disabled, the code above works perfectly to skip to a new track when a DJ disconnects. We just have to figure out what to do in the case of crossfades.

@mkody
Copy link

mkody commented Dec 22, 2021

I was also looking for a way to skip the track when switching to an another source. I got something working like this:

# Fade previous source and start next directly, then skip previous source
def dtr(a,b)
  thread.run.recurrent(delay=2., { source.skip(a) ; -1. })
  sequence([
    fade.out(duration=2., a),
    (b:source)
  ])
end

It's apparently (?) important to time the duration and delay together and to set it to 2..
If the delay is too short, we'll hear the song skip before it faded out. If it's too long we'll hear a bit of the previous track like Buster explained. And for what I have 2. is the answer.

Now, that works great if the next source is a request (here the playlist goes to a request that plays the jingle twice, then it goes back to the playlist): https://s.kdy.ch/dtr_dj-shows-dj.mp3

But when using an input.harbor() for live content, the first 5 seconds of the input are missing (here I say "click" and then count up in French, then disconnect a second later): https://s.kdy.ch/dtr_dj-live-dj.mp3

"Oh, I could use add() instead of sequence()" and yep, skip still works but it takes now 5 seconds for the previous track to actually fade out. https://s.kdy.ch/sdtr_dj-live-dj.mp3

def sdtr(a,b)
  thread.run.recurrent(delay=2., { source.skip(a) ; -1. })
  add(normalize=false, [
    fade.out(duration=2., a),
    (b:source),
  ])
end

And changing the (b:source) to sequence([blank(duration=5.), (b:source)]) just makes the same result as using my dtr() transition.

This 5 second problem could be discussed in an another issue - I might be missing something - but that wasn't an issue for us on 1.4.4 and I'm bringing it up here since we're talking about going live anyways.

@BusterNeece
Copy link
Author

Oh, that's an interesting idea...skipping the track upon the fade-in to the live broadcast, not on the fade-out, so it's just sitting there paused in the background basically, then when it fades, it fades to the new track directly.

That might actually help a lot in this case.

@BusterNeece
Copy link
Author

I'm noticing something, though, and perhaps this is something @toots should be aware of: any time, in these transitions, that I call thread.run.recurrent with any delay, 2. or 10. or whatever, it executes immediately instead.

Is this a known issue?

@mkody
Copy link

mkody commented Dec 24, 2021

Really? I added some logs at the start of my transition function and one inside the thread function and it did respect my delay, at least with the 2.0.1 docker image.

@BusterNeece
Copy link
Author

Yeah...I did the same thing, set up logging for both the outer and inner section, and it is delaying 2 seconds. And yet I'm still hearing the song sequence from one to the other before the live stream kicks in.

I wonder if that delay has to exceed the buffer setting for the Live broadcast, since that's what causes a delay in the stream kicking over to the live broadcast.

@BusterNeece
Copy link
Author

After further research, it appears that Liquidsoap will just pause the previous stream right where it is, regardless of what position it's in on skipping from one track to the next. So, you can either have this happen before the live stream starts or immediately afterward, but you can't remove that crossfade from happening, and there's apparently no setting that will make that possible.

It may be that, given the current code, the best we can hope to do is "hide" the crossfade from one song to another from the listener, by fading out before it actually happens, but that's a pretty shaky way of doing it and doesn't feel stable or reliable at all.

@toots
Copy link
Member

toots commented Dec 25, 2021

Hi all,

Thanks for these details. I believe that, at the core of it, there's something of an impossible need. In fact, crossfade sources always overlap beginning and end of tracks so it's impossible to get a clean skip at any time.

That is, unless you implement your own crossfading function that will be aware of your skip. Here we go:

# A source of requests
files = playlist("/tmp/pl")

# A reference to wether we're going to the live source
to_live = ref(false)

# Custom transition
def transition(old, new) =
  # If going to the live show, play a simple sequence
  if !to_live then
    sequence([fade.out(old.source),fade.in(new.source)])
  else
    # Otherwise, use the smart transition
    cross.smart(old, new)
  end
end

# Apply cross
radio = cross(transition, files)

# Live source
live = input.harbor("test")

# This is a _track sensitive_ fallback!
radio = fallback(id="live_fallback", replay_metadata=false, [live, radio])

# Trigger a track skip when live becomes available.
def check_live() =
  if live.is_ready() then
    # Only do that once:
    if not !to_live then
      to_live := true
      files.skip()
    end
   else
     # If not available, revert to radio songs
     to_live := false
  end
  true
end

# Continuously check on live.
radio = source.on_frame(radio, check_live)

This script seems to behave nicely. When the live becomes available, a skip is issued, directly to the underlying request source (always a better idea). This track skip generates a clean sequence of fade.out and fade.in due to the to_live reference being set to true.

At the fallback level, the operator will end for the request source to finish its track with a fade.out then switch to the live.

Then, when live becomes unavailable again, the fallback will resume with the second half of the sequence, i.e. a clean fade.in on the new track. At this point, to_live is set to false so all the subsequent transitions will use the usual crossfade transition.

cross.smart has the same parameters as crossfade I believe so you can just put them there. Or you can use cross.simple to use the same transition as crossfade in simple mode.

@savonet savonet locked and limited conversation to collaborators Dec 25, 2021
@toots toots converted this issue into discussion #2123 Dec 25, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Projects
None yet
Development

No branches or pull requests

3 participants