1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-03 17:59:24 +02:00

Send playback position as player event (#1495)

* Send play progress as PlayerEvent::PositionChanged

* Replaced PlayerEvent::PositionChanged with set_progress_callback() method

* Revert "Replaced PlayerEvent::PositionChanged with set_progress_callback() method"

This reverts commit f26e3de07b.

* Added opt-in config in PlayerConfig for progress event

* Added doc comments and set default position interval to 1sec for standalone

* Remove handling of PositionChanged in standalone binary

* Fixed wrong event handling
This commit is contained in:
Thomas Schlage 2025-05-03 23:37:17 +02:00 committed by GitHub
parent 6bdc0eb312
commit d12e1b8549
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 34 additions and 0 deletions

View file

@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [connect] Add `volume_steps` to `ConnectConfig` (breaking) - [connect] Add `volume_steps` to `ConnectConfig` (breaking)
- [connect] Add and enforce rustdoc - [connect] Add and enforce rustdoc
- [playback] Add `track` field to `PlayerEvent::RepeatChanged` (breaking) - [playback] Add `track` field to `PlayerEvent::RepeatChanged` (breaking)
- [playback] Add `PlayerEvent::PositionChanged` event to notify about the current playback position
- [core] Add `request_with_options` and `request_with_protobuf_and_options` to `SpClient` - [core] Add `request_with_options` and `request_with_protobuf_and_options` to `SpClient`
- [oauth] Add `OAuthClient` and `OAuthClientBuilder` structs to achieve a more customizable login process - [oauth] Add `OAuthClient` and `OAuthClientBuilder` structs to achieve a more customizable login process

View file

@ -139,6 +139,9 @@ pub struct PlayerConfig {
// pass function pointers so they can be lazily instantiated *after* spawning a thread // pass function pointers so they can be lazily instantiated *after* spawning a thread
// (thereby circumventing Send bounds that they might not satisfy) // (thereby circumventing Send bounds that they might not satisfy)
pub ditherer: Option<DithererBuilder>, pub ditherer: Option<DithererBuilder>,
/// Setting this will enable periodically sending events during playback informing about the playback position
/// To consume the PlayerEvent::PositionChanged event, listen to events via `Player::get_player_event_channel()``
pub position_update_interval: Option<Duration>,
} }
impl Default for PlayerConfig { impl Default for PlayerConfig {
@ -156,6 +159,7 @@ impl Default for PlayerConfig {
normalisation_knee_db: 5.0, normalisation_knee_db: 5.0,
passthrough: false, passthrough: false,
ditherer: Some(mk_ditherer::<TriangularDitherer>), ditherer: Some(mk_ditherer::<TriangularDitherer>),
position_update_interval: None,
} }
} }
} }

View file

@ -85,6 +85,7 @@ struct PlayerInternal {
player_id: usize, player_id: usize,
play_request_id_generator: SeqGenerator<u64>, play_request_id_generator: SeqGenerator<u64>,
last_progress_update: Instant,
} }
static PLAYER_COUNTER: AtomicUsize = AtomicUsize::new(0); static PLAYER_COUNTER: AtomicUsize = AtomicUsize::new(0);
@ -195,6 +196,14 @@ pub enum PlayerEvent {
track_id: SpotifyId, track_id: SpotifyId,
position_ms: u32, position_ms: u32,
}, },
/// Requires `PlayerConfig::position_update_interval` to be set to Some.
/// Once set this event will be sent periodically while playing the track to inform about the
/// current playback position
PositionChanged {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
},
Seeked { Seeked {
play_request_id: u64, play_request_id: u64,
track_id: SpotifyId, track_id: SpotifyId,
@ -481,6 +490,7 @@ impl Player {
player_id, player_id,
play_request_id_generator: SeqGenerator::new(0), play_request_id_generator: SeqGenerator::new(0),
last_progress_update: Instant::now(),
}; };
// While PlayerInternal is written as a future, it still contains blocking code. // While PlayerInternal is written as a future, it still contains blocking code.
@ -1340,6 +1350,22 @@ impl Future for PlayerInternal {
position_ms: new_stream_position_ms, position_ms: new_stream_position_ms,
}); });
} }
if let Some(interval) =
self.config.position_update_interval
{
let last_progress_update_since_ms =
now.duration_since(self.last_progress_update);
if last_progress_update_since_ms > interval {
self.last_progress_update = now;
self.send_event(PlayerEvent::PositionChanged {
play_request_id,
track_id,
position_ms: new_stream_position_ms,
});
}
}
} }
Err(e) => { Err(e) => {
error!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e); error!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e);

View file

@ -1818,6 +1818,7 @@ fn get_setup() -> Setup {
normalisation_release_cf, normalisation_release_cf,
normalisation_knee_db, normalisation_knee_db,
ditherer, ditherer,
position_update_interval: None,
} }
}; };

View file

@ -243,6 +243,8 @@ impl EventHandler {
); );
env_vars.insert("FILTER", filter.to_string()); env_vars.insert("FILTER", filter.to_string());
} }
// Ignore event irrelevant for standalone binary like PositionChanged
_ => {}
} }
if !env_vars.is_empty() { if !env_vars.is_empty() {