mirror of
https://github.com/librespot-org/librespot.git
synced 2026-04-27 08:15:50 +03:00
[GH-ISSUE #584] [Tokio migration] Sendness of Sinks #371
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#371
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 @Johannesd3 on GitHub (Jan 26, 2021).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/584
In the current version,
Sinks are allowed not to beSend. While this is currently only the case for Rodio, it seems to have a reason. Therefore, thePlayerInternalfuture can't be executed by Tokio but must run in its own thread. Previously this was possible withFuture::wait, but apparently they removed it.Perhaps it would be the best to rewrite PlayerInternal in a synchronous manner. It's confusing anyway to have a blocking future. There are still a few places where an interaction with async functions is needed. I don't know how to solve this best...
@Johannesd3 commented on GitHub (Feb 3, 2021):
First of all, there is a solution to make the
RodioSinkSend: aRodioSinkconsists of arodio::Sinkand arodio::OutputStream. TheSinkisSend, but theOutputStreamisn't. TheOutputStreamis only needed at creation of theSink, but must not be dropped while the sound is playing.The solution: spawn a thread, create
OutputStreamandSinkin there, send theSinkviaoneshotback. Next, the thread waits for anotheroneshotin the other direction which is fired when theRodioSinkis dropped. After receiving this, theOutputStreamgets dropped and the background thread ends. Since the thread is sleeping most of the time, it wouldn't impact performance.Nevertheless, I think the current player doesn't work well with Rodio. Currently, the player stuffs the audio as fast as possible into the sink. Rodio works different, it wants an iterator of samples (
i16) asSourceand polls this iterator from a background thread. I want to suggest a rewrite of the playback crate that is better compatible with rodio:Introduce some traits:
Track: Iterator<Item = Vec<i16>>, which represents a loaded track. As iterator it yields the decoded audio, and it has some other functions, e.g.seekorset_stream_mode.Queue, which has a functionload_next_trackreturning aTrack. There might also be functions to trigger preloading, to skip to the previous track, etc.Sink, which can beplayed,paused andstopped, e.g. a rodio sink or a stdout/pipe sink.The
struct PlayerInternal, which was previously aFuture, shall become anIteratorofi16s orVec<i16>s instead. It contains aQueueand the currently playingTrack. The audio it yields as iterator comes from the currentTrack. If a track is over, it loads the next track from itsQueue. It is able to receive commands and emit events via channels. Seeking and skipping, but not playing and pausing. It also keeps track of the time: If the playback is behind, an event with the corrected time is sent.A
Sinkis now created by passing aPlayerInternalto it. If it is playing, theSink"polls" thePlayerInternal, or does nothing if it is paused. So thePlayerInternalis responsible for what is played, and theSinkis responsible for when it is played.Now, a
Playeris just a small wrapper containing aSinkand a sender forPlayerInternal's commands. It supports already the usual commandsplay,pause,seeketc., but there are still some drawbacks regarding events, since the player has no own thread/task. A pause event wouldn't be possible, since thePlayerInternalwouldn't get polled if theSinkis paused. It also wouldn't be possible for thePlayerto stop theSinkwhen thePlayerInternalsignals that theQueueis over, which should be done to avoid waste of resources.So the next step would be a wrapper around
Playerthat spawns its own thread/task, has channels to receive commands and send events, and handles the events ofPlayerInternalin the right way. It could either be async or blocking.This would be totally independent of Librespot, so this could happen in its own crate. Only the
TrackandQueuetraits have to be implemented by librespot.However, I'm not sure if this is better or simpler than the current solution, so what do you think of this idea?
core#890