- 
                Notifications
    
You must be signed in to change notification settings  - Fork 755
 
Add MPRIS support #1596
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
base: dev
Are you sure you want to change the base?
Add MPRIS support #1596
Changes from all commits
8b0f69f
              fc6d797
              52c371f
              64f0606
              194cd70
              14c06c7
              8f98a34
              c32bc88
              e9987c9
              fdc4e99
              0e933f6
              121b4fd
              29cd843
              605553a
              6748e45
              8297fdd
              90cd456
              8e15bef
              afd6e8c
              da6ede9
              f583f10
              1e4dd30
              d500285
              b60fb08
              c6f096b
              d50b902
              043e82c
              0950bd5
              17891cf
              32ffb1a
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -37,7 +37,7 @@ repository = "https://github.com/librespot-org/librespot" | |
| edition = "2024" | ||
| 
     | 
||
| [features] | ||
| default = ["native-tls", "rodio-backend", "with-libmdns"] | ||
| default = ["native-tls", "rodio-backend", "with-libmdns", "with-mpris"] | ||
| 
     | 
||
| # TLS backends (mutually exclusive - compile-time checks in oauth/src/lib.rs) | ||
| # Note: Feature validation is in oauth crate since it's compiled first in the dependency tree. | ||
| 
          
            
          
           | 
    @@ -133,6 +133,10 @@ with-dns-sd = ["librespot-discovery/with-dns-sd"] | |
| # data. | ||
| passthrough-decoder = ["librespot-playback/passthrough-decoder"] | ||
| 
     | 
||
| # MPRIS: Allow external tool to have access to playback | ||
| # status, metadata and to control the player. | ||
| with-mpris = ["dep:zbus", "dep:zvariant"] | ||
| 
     | 
||
| [lib] | ||
| name = "librespot" | ||
| path = "src/lib.rs" | ||
| 
          
            
          
           | 
    @@ -181,7 +185,10 @@ tokio = { version = "1", features = [ | |
| "sync", | ||
| "process", | ||
| ] } | ||
| time = { version = "0.3", features = ["formatting"] } | ||
| url = "2.2" | ||
| zbus = { version = "5", default-features = false, features = ["tokio"], optional = true } | ||
| zvariant = { version = "5", default-features = false, optional = true } | ||
| 
     | 
||
| [package.metadata.deb] | ||
| maintainer = "Librespot Organization <[email protected]>" | ||
| 
          
            
          
           | 
    ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -41,6 +41,11 @@ use url::Url; | |
| mod player_event_handler; | ||
| use player_event_handler::{EventHandler, run_program_on_sink_events}; | ||
| 
     | 
||
| #[cfg(feature = "with-mpris")] | ||
| mod mpris_event_handler; | ||
| #[cfg(feature = "with-mpris")] | ||
| use mpris_event_handler::MprisEventHandler; | ||
| 
     | 
||
| fn device_id(name: &str) -> String { | ||
| HEXLOWER.encode(&Sha1::digest(name.as_bytes())) | ||
| } | ||
| 
          
            
          
           | 
    @@ -270,6 +275,7 @@ async fn get_setup() -> Setup { | |
| #[cfg(feature = "passthrough-decoder")] | ||
| const PASSTHROUGH: &str = "passthrough"; | ||
| const PASSWORD: &str = "password"; | ||
| const POSITION_UPDATE_INTERVAL: &str = "position-update-interval"; | ||
| const PROXY: &str = "proxy"; | ||
| const QUIET: &str = "quiet"; | ||
| const SYSTEM_CACHE: &str = "system-cache"; | ||
| 
          
            
          
           | 
    @@ -315,6 +321,7 @@ async fn get_setup() -> Setup { | |
| #[cfg(feature = "passthrough-decoder")] | ||
| const PASSTHROUGH_SHORT: &str = "P"; | ||
| const PASSWORD_SHORT: &str = "p"; | ||
| const POSITION_UPDATE_INTERVAL_SHORT: &str = ""; // no short flag | ||
| const EMIT_SINK_EVENTS_SHORT: &str = "Q"; | ||
| const QUIET_SHORT: &str = "q"; | ||
| const INITIAL_VOLUME_SHORT: &str = "R"; | ||
| 
          
            
          
           | 
    @@ -625,6 +632,12 @@ async fn get_setup() -> Setup { | |
| "Knee width (dB) of the dynamic limiter from 0.0 to 10.0. Defaults to 5.0.", | ||
| "KNEE", | ||
| ) | ||
| .optopt( | ||
| POSITION_UPDATE_INTERVAL_SHORT, | ||
| POSITION_UPDATE_INTERVAL, | ||
| "Maximum interval in ms for player to send a position event. Defaults to no forced position update.", | ||
| "POSITION_UPDATE", | ||
| ) | ||
| .optopt( | ||
| ZEROCONF_PORT_SHORT, | ||
| ZEROCONF_PORT, | ||
| 
          
            
          
           | 
    @@ -1800,6 +1813,22 @@ async fn get_setup() -> Setup { | |
| }, | ||
| }; | ||
| 
     | 
||
| let position_update_interval = opt_str(POSITION_UPDATE_INTERVAL).as_deref().map( | ||
| |position_update| match position_update.parse::<u64>() { | ||
| Ok(value) => Duration::from_millis(value), | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe also add a lower bound here? Either by returning an error if lower, or maybe better by simply taking the  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any idea, what sensible value should we use? 100ms seems quite sensible lower bound.  | 
||
| _ => { | ||
| invalid_error_msg( | ||
| POSITION_UPDATE_INTERVAL, | ||
| POSITION_UPDATE_INTERVAL_SHORT, | ||
| position_update, | ||
| "Integer value in ms", | ||
| "None", | ||
| ); | ||
| exit(1); | ||
| } | ||
| }, | ||
| ); | ||
| 
     | 
||
| #[cfg(feature = "passthrough-decoder")] | ||
| let passthrough = opt_present(PASSTHROUGH); | ||
| #[cfg(not(feature = "passthrough-decoder"))] | ||
| 
        
          
        
         | 
    @@ -1818,7 +1847,7 @@ async fn get_setup() -> Setup { | |
| normalisation_release_cf, | ||
| normalisation_knee_db, | ||
| ditherer, | ||
| position_update_interval: None, | ||
| position_update_interval, | ||
| } | ||
| }; | ||
| 
     | 
||
| 
          
            
          
           | 
    @@ -1991,6 +2020,14 @@ async fn main() { | |
| } | ||
| } | ||
| 
     | 
||
| #[cfg(feature = "with-mpris")] | ||
| let mpris = MprisEventHandler::spawn(player.clone(), &setup.connect_config.name, None) | ||
| .await | ||
| .unwrap_or_else(|e| { | ||
| error!("could not initialize MPRIS: {e}"); | ||
| exit(1); | ||
| }); | ||
| 
     | 
||
| loop { | ||
| tokio::select! { | ||
| credentials = async { | ||
| 
          
            
          
           | 
    @@ -2044,6 +2081,10 @@ async fn main() { | |
| exit(1); | ||
| } | ||
| }; | ||
| 
     | 
||
| #[cfg(feature = "with-mpris")] | ||
| mpris.set_spirc(spirc_.clone()); | ||
| 
     | 
||
| spirc = Some(spirc_); | ||
| spirc_task = Some(Box::pin(spirc_task_)); | ||
| 
     | 
||
| 
          
            
          
           | 
    @@ -2089,6 +2130,9 @@ async fn main() { | |
| 
     | 
||
| let mut shutdown_tasks = tokio::task::JoinSet::new(); | ||
| 
     | 
||
| #[cfg(feature = "with-mpris")] | ||
| shutdown_tasks.spawn(mpris.quit_and_join()); | ||
| 
     | 
||
| // Shutdown spirc if necessary | ||
| if let Some(spirc) = spirc { | ||
| if let Err(e) = spirc.shutdown() { | ||
| 
          
            
          
           | 
    ||
Uh oh!
There was an error while loading. Please reload this page.