rodio: Cannot restart stopped sink

Hi there! Thank you for this awesome library. While exploring the API I stumbled over the following behavior, which I believe is a bug:

A Sink no longer plays newly appended sounds after .stop() has been called on the Sink.

Looking at the sources, it seems that .stop() sets Controls.stopped to true, but I could not find anything that would set it back to false other than constructing a new Sink.

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 4
  • Comments: 15 (3 by maintainers)

Commits related to this issue

Most upvoted comments

I implore you to revisit that design decision. I think this is needlessly convoluted. Besides, when do you ever want to just call something on an object which makes it unusable but doesn’t drop it?

stop() should work like you would expect and according to the current docs: Clear the queue and reset the sink.

The code of Sink is very very simple, and this struct exists mainly to make it easier to get started with rodio. In other words, using a Sink is not the primary way you’d play a sound in a production app. In order to get a behaviour different from the default one, the way to go is to copy-paste the code of Sink and modify it.

Shouldn’t .stop() actually behave in an intuitive manner if Sink is supposed to “make it easier to get started with rodio”? Clearing the queue of sounds should be basic functionality. And to be honest many apps would not need to look any further than Sink if a .clear() like function was implemented. At least it should be made absolutely clear that .stop() makes a sink useless, even though it is redundant considering .drop() exists…

I’m sorry if I sound a bit annoyed (because I am), I just spent a solid hour looking at the docs before coming here. I don’t agree with that design decision, but if we shall stick with it, it should be abundantly clear in the docs how .stop() works.

So can we talk about why the API is designed in such way? It seems counter-intuitive that you get an unusable sink after you call .stop() on it. Am I supposed to create a new sink every time I want to restart playback? If so, why even expose .stop() method, why not just Drop, since the Sink is useless after calling it anyway?

Well okay then. While I can’t say I agree with this, it seems like the most reasonable way to do this. The reason why I think it’s unreasonable is because the Sink struct hides some of the lower level implementation details which are then exposed to the user who wants to reimplement their own version of Sink, just because they need this .clear() functionality.

Additionaly, I would like to ask you to reflect this intention in the docs. I for one tend to take libraries mostly as an opaque object that I shouldn’t mess with if it isn’t really important/broken. This breaks the illusion and I think it’s best to inform the user.

So, I’m trying to work through a solution right now. My idea is to have another impl Iterator struct like Stoppable for sources, and to make it Skippable. The iterator’s next() will run the inner iterator’s next() method if skip is false, and consume the entire iterator if it is true. This would make the sink move on to the next source on its list, or stop if it runs out.

The sink would have a skip() method which sets the current iterator’s Skippable struct field to true, and the clear() method would simply skip() as many times as there are sources, and then pause() itself so it outputs zeros.

How does this sound so far? I struggle with handling some of the concurrency bits, but this seems like a reasonable approach to take, and would create two new useful methods instead of just the one.

@TheEdward162 These are excellent questions.

Working around the stop == drop issue in ggez had me do some ugly things with context and default devices, that will probably bite someone in the future. This could have been easily avoided with different stop semantics.

I’m wondering now, since .stop() apparently needs to behave like a drop(), couldn’t we get another function (e.g. sink.clear()) that just clears the queue while leaving the sink operational?

I think I’m misunderstanding something obvious. To clarify, if I stop one sound and then append a different one I’m not supposed to hear it? In other words I cannot reuse a Sink? (I thought the purpose of Sink::stop() was emptying the queue so that other sounds could be played.)

Sorry if my questions are annoying. I’m somewhat confused at the moment 😃