-
Notifications
You must be signed in to change notification settings - Fork 695
Send playback position as player event #1495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Anyhow, could you please explain your use case in detail so that I can better understand your situation and requirement of this addition? |
I'm fine to make this configurable. I wasn't sure if I should add a prop to PlayerConfig or create some kind of callback setter function to only notify the callback. My use case is that I have built a Music player for my son where he can play preselected Tracks. Part of every player are controls like play, pause, next track, prev track and a progress bar with the option to seek to a specific point in the track. Similar to the spotify interface. |
Yeah, we don't send this position constantly, because it seems like an information overload. There is also no need to do so when you know if the playback state in my opinion? For example you could write a simple loop like the following:
async fn progress(mut channel: librespot::playback::player::PlayerEventChannel) {
use librespot::playback::player::PlayerEvent;
use std::time::Duration;
const ONE_SEC_INTERVAL: Duration = Duration::from_secs(1);
let mut is_playing = false;
let mut progress = Duration::default();
loop {
tokio::select! {
channel_event = channel.recv() => {
match channel_event {
None => break,
Some(event) => {
progress = update_position(&event).unwrap_or(progress);
is_playing = update_playing(&event).unwrap_or(is_playing);
}
}
},
_ = tokio::time::sleep(ONE_SEC_INTERVAL), if is_playing => {
progress += ONE_SEC_INTERVAL;
let min = progress.as_secs() / 60;
let sec = progress.as_secs() % 60;
log::info!("progress (playing {is_playing}): {min}:{sec:0<2}", )
}
}
}
log::info!("progress tracker stopped");
fn update_position(event: &PlayerEvent) -> Option<Duration> {
match event {
PlayerEvent::Playing { position_ms, .. }
| PlayerEvent::Paused { position_ms, .. }
| PlayerEvent::Seeked { position_ms, .. } => {
Some(Duration::from_millis((*position_ms).into()))
}
_ => None,
}
}
fn update_playing(event: &PlayerEvent) -> Option<bool> {
match event {
PlayerEvent::Playing { .. } | PlayerEvent::Stopped { .. } => Some(false),
PlayerEvent::Paused { .. } => Some(true),
_ => None,
}
}
} Adding the position update event wouldn't do any good from my point of view. @roderickvd, @kingosticks do you have any opinion on the topic? |
I think a way to query the current position is really useful and a noticeable omission from our API. What you have there will drift from the actual position over time, it'll be more noticeable for long songs, probably unusable on a busy system when that tokio timer doesn't get to fire monotonically. For what it's worth, I have previously tried to keep position state in this way for a player. It didn't work very well and drifted surprisingly quickly, the solution was to periodically query for the actual position. |
I experience the same. The simple fact that the processing of the data consumes time and is not realtime already leads to differences between a tokio timer. I could not achieve an accurate result as well. With my previous solution using spotifyd I did poll the spotify API every 10 seconds as the API call is also quite expensive and slow. With librespot this works way smoother and a progress update should be doable imo :) I did change the solution to a opt-in by setting a progress callback. Does this solution fit better in your opinion? I have to admit, that it feels like a broken design mixing event consumption to get the duration and callbacks to get the position^^ Btw, this is my code using the player right now
I basically store the duration and position in a object which I can poll every second from my progress bar update interval. |
Hmm, yeah. Maybe I was a bit to focused on "everything is already there what you need to get the correct position". Sometimes an update might just be a simpler and handier solution then one might think. @fragsalat You didn't had to redesign the whole thing (especially if it doesn't fit well in the existing flow/logic). As is (with the new changes), it seems fine, but if you did like the Event solution more (because, well it did seem to fit better overall), then just change it back and add an option to opt-in the position updates (probably
|
I mean you are the maintainers and I would seek your opinion here :) |
…ack() method" This reverts commit f26e3de.
I'm not too much a fan of sending messages every so often, which seems wasteful. Instead, I'd vote for either the callback route, or storing the timestamp gotten from each |
That was another option, to store the position in the PlayerInternal and make it available via a getter in the Player struct. I could add this and leave the refactoring later to you if you don't mind :) PS: Are you doing squash merges or merge commits? If not squashing I would cleanup the commit history in this PR. |
Policy is to squash merging, and keeping PR history intact for ease of reviewing. |
Did for now revert to the event approach with opt-in config. I figured out the PlayerInternal is not easily accessible from the Player struct. There is just a channel into the player thread but none in the other direction which could allow me to access the position. I guess thats part of the refactoring you were speaking of. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far (as far as the current construct allows it to be at least hehe).
Nice and low key implementation! Don't forget to update the changelog and - down the line - the wiki. |
The changelog already has an entry in this PR and in regards to the Wiki I'm not sure as the Wiki does not have information related to the usage as library but only to the usage of the standalone binary. As your team decided to not offer this feature for the binary I wouldn't know what to update there. So if you have a hint where to update, I'm happy to do so :) |
Yes, you are right. Thanks. |
Awesome. I wanted to thank you all for this great collaboration and your active maintenance and effort in this nice library <3 |
Fixing #1493
Hey there, I did an example implementation how the playback position could be sent to the user of the Player struct.
The change should be backward compatible and I did throttle the sending of events by only informing every 250ms.
Please tell me in case you prefer a different solution or would like to change something else.