mirror of
https://github.com/librespot-org/librespot.git
synced 2026-04-27 08:15:50 +03:00
[GH-ISSUE #521] Tracks skip after few seconds when piping passthrough audio #332
Labels
No labels
A-Alsa
SpotifyAPI
Tokio 1.0
audio
bug
can't reproduce
compilation
dependencies
duplicate
enhancement
good first issue
help wanted
high priority
imported
imported
invalid
new api
pull-request
question
reverse engineering
wiki
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/librespot#332
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @magnetised on GitHub (Aug 29, 2020).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/521
This is a re-surfacing of
https://github.com/plietar/librespot/issues/231 and
https://github.com/librespot-org/librespot/issues/54
I'm trying to use librespot to stream audio from spotify over a pipe backend into my multiroom audio system (in Elixir). The data is flowing and everything basically works from an integration point of view (data flows into the elixir process and nothing crashes), but the spotify player skips to the next track every few seconds.
Reading the above issues, it seems that this is because librespot sends an inappropriate notification to the spotify player once it has downloaded all the data for a song, which in the case of the pipe backend happens within a few seconds (as the pipe backend just pulls the data in as fast as the network will let it).
I'm using librespot master
HEADas my source and compiling withcargo build --release --no-default-featuresReally keen to get this working please let me know how I can help.
@tadly commented on GitHub (Sep 27, 2020):
After some time I finally found the problem. You have to "pace" reading from stdout.
I have to sleep for 2 seconds before reading the next batch. Less than 2 sec. and the skipping happens again.
Edit: I lied it seems. Less works as well as long as the amount of bits read from stdout matches. Still experimenting :)
@rystaf commented on GitHub (Oct 4, 2020):
@tadly Do you have an example of how to achieve that?
@tadly commented on GitHub (Oct 4, 2020):
@rystaf hope you can read python :P
This is what I used to test some stuff and is not optimal but it works.
It spawns an HTTP server which you can connect to and have audio playing.
Either use your browser and go to http://localhost:8000 or use any other audio player that supports streaming.
Edit:
The relevant part from below regarding the pacing aspect is this:
Fully working example implementation
@magnetised commented on GitHub (Oct 7, 2020):
I've been poking around at this too. IMHO the problem is a result of the player using the byte-offset within the audio stream being used to directly represent the playback position. This works when the sink write is basically synchronous (I've done some measurements and the time taken to write to the rodio backend is basically the duration of the sample being written). But obviously with the pipe backend, the duration of the write is basically zero.
The approach above adds a delay to the pipe write by slowing down the read - since the pipe will block until read. My system is using erlang to read from a fifo pipe and I just receive messages when new data comes in, so I don't have that option.
Instead I've tried adding a delay to the pipe write which tries to match the expected duration of the audio being written - basically emulating the behaviour of the other sinks.
You can see the change here: https://github.com/librespot-org/librespot/compare/dev...magnetised:pipe-playback-delay
This works well enough - the backend occasionally falls behind the expected position, I'm guessing due to cumulative delays because of imprecise sleep durations - but I've minimised this by using a tight loop of small sleeps, rather than just a single sleep call of the required duration.
I'm mystified why I can't just use the right value for the duration of the sample - ~22.6 microseconds per sample - if I use that then the playback is constantly rewinding. Maybe there's something obvious I'm missing there though.
In reality I would prefer to re-write the player to use time-based calculations for the track position. This would allow me to do some buffering within my application since I'd have access to excess audio data, rather than just enough to play.
I suspect that the reason for using the byte position, enforced by the synchronous sink write function, is to make sure that the system stays in sync with the actual playback speed of the audio system, rather than assuming a 1:1 ratio between the system clock and the playback speed. Would you agree?
If I moved to using time-based position, I think it would involve tweaking the player to add a clock based position measurement in the play loop and removing any handling of the end of the audio download. Is that something you'd accept, or is it not worth my effort? (The player code is fairly involved and I have a horrible feeling there would be a lot of edge cases to handle).
@michaelherger commented on GitHub (Oct 11, 2020):
@magnetised - thanks for the analysis! I've been struggling with this issue, too. In the end I did a dirty hack. Yours seems nicer to me. Have you been using it successfully?
@magnetised commented on GitHub (Oct 12, 2020):
@michaelherger a bit... my rough tests over a couple of hours were prett successful - the tracks kept playing until the progress at in the Spotify app reached the end and there weren't that many skips backwards. So at that level it works (for me).
I don't like the "14" magic number though so would like to be sure I wasn't just solving for my particular laptop rather than a generic solution. Maybe if you get a chance you could see how it works for you - that would double the amount of data available 😅
One problem is the lack of info sent to the sink (it just gets PCM data) which is fair enough but prevents stuff like analysing the track duration and buffering.
I suppose I could experiment with beefing up the sink API a little to enable this stuff (for most backends it would just be a no-op for the additional calls) but it feels bed to mess up the super simple version that's there.
@kingosticks commented on GitHub (Oct 12, 2020):
I agree we should pace the pipe writes a bit, it should not need to be super accurate because no consumer should be relying on that. And since the pipe output doesn't need to support using it for anything except playback, changing it to write at the normal playback rate (ish) should be fine. The current system of pacing the writes (and therefore librespot's apparent current playback position) by relying on the pipe getting full is not very nice. Isn't it a bit surprising nobody has complained about this until now?
Assuming the writing is paced, why do you need extra information to implement an input buffer (other than the fixed sample rate and size)? Does your program really not already have an input buffer between the read end of the pipe and the output device you are writing? What would you do with that extra information?
@codetheweb commented on GitHub (Oct 26, 2020):
I'm having a similar problem.
I'm writing an adaption layer in Rust using Neon for Node.js so that code in Node.js can call librespot functions. I wrote a custom sink for Player like so:
Another part then uses the Receiver side of the mpsc channel to hand off audio data to Node.js when a poll function is called from JS. The issue is that I can't figure out how to put backpressure on the sink / channel; streaming with Spotify Connect results in track skipping. Any ideas on how to fix this? @magnetised's solution didn't seem to work for me.
Apologies if this is the wrong place to ask, I'm very new to Rust.
@ashthespy commented on GitHub (Oct 26, 2020):
@codetheweb That is a cool project, I'd be quite interested in something similar. Is your code up somewhere I can take a peek it at?
@codetheweb commented on GitHub (Oct 26, 2020):
I'll try to clean it up and post it later tonight.
I accidentally committed my credentials in my local repo at some point... 😛
I plan to make a high-level interface available with play/pause/seek/enable connect/event emitters etc. Use case is a Spotify Connect Discord music bot. I've been wanting to learn Rust for a while and this seemed like a good project to get my feet wet.
@codetheweb commented on GitHub (Oct 26, 2020):
@ashthespy check it out here: https://github.com/codetheweb/librespot-node.
Like I said, it's still extremely rough around the edges. Playing a song by track ID works fine (because it doesn't matter how much is buffered) but spirc is kinda broken.
I based it on this repo, which played audio directly using the default sink instead of passing it back to Node.js.
An example script (put in
dev.jsinsrc/):If you wanna talk further, feel free to open an issue over there or use one of the contact methods listed on my site.
@Burningstone91 commented on GitHub (Dec 10, 2020):
I think I have the same issue. After a few days of fiddling around I finally managed to get librespot running and the data is feed to a pipe, which is read by snapserver running on the same machine. However as soon as I start playing a song it skips immediately to the next song, not even 1 second, and the pipe grew to a few gigs in less than a minute. I can provide some logs if this helps.
I really like to get this to work, it's the last piece missing for my multiroom solution and highly appreciate any help with this.
@gjdawson commented on GitHub (Mar 1, 2021):
I was curious about this, as it's impacting something I've been working on as well. A little testing has shown that the problem may only emerge when the
--passthroughflag is present.@Johannesd3 commented on GitHub (Mar 1, 2021):
Well, that's not possible. When the issue was created
--passthroughdid not even exist.@gjdawson commented on GitHub (Mar 1, 2021):
Well ain't that something. It's what I'm seeing at the moment, though: Without passthrough, piping audio out works fine. With it, I get skipping. I'm at a loss to explain it.
@Johannesd3 commented on GitHub (Mar 1, 2021):
It may depend on where you pipe it to. Something like
pacathas just a little buffer, so everything will be fine. If you use a fifo or a file, they could grow very fast, and after a short while the player reaches the end of the track.@roderickvd commented on GitHub (Jun 14, 2021):
Can you let us know what you're piping to? Or if this is no longer an issue?
@Johannesd3 commented on GitHub (Jun 14, 2021):
Try
/dev/nullto reproduce it quickly.@roderickvd commented on GitHub (Aug 7, 2021):
Closing, no further feedback.