Git Product home page Git Product logo

souvlaki's People

Contributors

0323pin avatar 101100 avatar ayyeve avatar eladyn avatar jpochyla avatar klemensn avatar literallyjustroy avatar sinono3 avatar ssttevee avatar yxl76 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

souvlaki's Issues

Handle Access is denied error on Windows from not running as Administrator

I'm trying to handle the windows error from MediaControls::new that it panics when the program isn't run as Administrator, but I don't see how to convert the error to a WindowsError, only from.

I also can't seem to use error-stack IntoReport on souvlaki errors like other errors, they don't implement std::error::Error.

The Administrator requirement may be from using the existing HWND created by the terminal when opening my program, because I don't spawn a window myself.

#[cfg(not(target_os = "windows"))]
let hwnd = None;

#[cfg(target_os = "windows")]
let hwnd = {
    use windows::Win32::System::Console::GetConsoleWindow;

    let console_window = unsafe { GetConsoleWindow() };
    let hwnd = console_window.0 as *mut c_void;
    Some(hwnd)
};

let platform_config = PlatformConfig {
    dbus_name: "VRC_OSC",
    display_name: "VRC-OSC",
    hwnd,
};

let controls = match MediaControls::new(platform_config) {
    Ok(controls) => controls,
    Err(error) => {
        // I can see the error code when printing but I can't access it
        println!("{:?}", error);
        panic!("{error:?}")
    }
};

EDIT: I can't seem to set_playback for sources other than my own with set_metadata, is it possible to do this?

[docs] `SetVolume` says values will be 0.0 to 1.0, but can actually be more

I have just tried implementing this, and noticed that the documentation says:

souvlaki/src/lib.rs

Lines 45 to 46 in fc88dfb

/// Sets the volume from 0.0 to 1.0.
SetVolume(f64),

but actually souvlaki does not clamp the value and the inputted value can be anything above 0, as per the mpris documentation:

Note that the volume may be higher than 1.0, although generally clients should not attempt to set it above 1.0.

source

example: playerctl volume 100 will give float 100.0

Linux: Shuffle/Loop

so I am on kde plasma and I think a few other OS's have this as a option. but it would be nice if I could change the loop state of my music or shuffle easily. (if this allready exists excuse my ignorance)

Support for other platforms

Hi,

Today I wanted to build spotify-player with the media-control feature enabled on NetBSD. But, I realized that souvlaki only supports Windows, Linux and MacOS.

Any chance to add support for other platforms where MPRIS/dbus is supported? I don't really see what could be Linux specific in src/platforms/linux/mod.rs. Maybe unix.rs would be a possibility? Or, just define target_os = "netbsd" and use the same mod.rs?

Thanks!

error: unexpected token: `include_str`

Build failing on arch linux

$ rustc --version
rustc 1.49.0 (e1884a8e3 2020-12-29)
$ cargo build --release
    Updating crates.io index
  Downloaded libc v0.2.100
  Downloaded 1 crate (528.2 KB) in 0.75s
   Compiling pkg-config v0.3.19
   Compiling libc v0.2.100
   Compiling souvlaki v0.4.1 (/home/chiller/Desktop/git-extern/souvlaki)
   Compiling libdbus-sys v0.2.1
   Compiling dbus v0.9.3
   Compiling dbus-crossroads v0.4.0
error: unexpected token: `include_str`
 --> src/lib.rs:1:10
  |
1 | #![doc = include_str!("../README.md")]
  |          ^^^^^^^^^^^

error: aborting due to previous error

error: could not compile `souvlaki`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed

Implement missing metadata

Here's a list of all metadata elements for each platform. There are so much of these that we'll miss a lot of them if we just implement the intersection of all of them. My idea is to later separate each platform implementation in their own crate, with all of the features, and keep a cross-platform version on souvlaki.

MPRIS:

  • mpris:trackid (D-Bus path): A unique identity for this track within the context of an MPRIS object (eg: tracklist).
  • mpris:length (64-bit integer): The duration of the track in microseconds.
  • mpris:artUrl (URI): The location of an image representing the track or album. Clients should not assume this will continue to exist when the media player stops giving out the URL.
  • xesam:album (String): The album name.
  • xesam:albumArtist (List of Strings): The album artist(s).
  • xesam:artist (List of Strings): The track artist(s).
  • xesam:asText (String): The track lyrics.
  • xesam:audioBPM (Integer): The speed of the music, in beats per minute.
  • xesam:autoRating (Float): An automatically-generated rating, based on things such as how often it has been played. This should be in the range 0.0 to 1.0.
  • xesam:comment (List of Strings): A (list of) freeform comment(s).
  • xesam:composer (List of Strings): The composer(s) of the track.
  • xesam:contentCreated (Date/Time): When the track was created. Usually only the year component will be useful.
  • xesam:discNumber (Integer): The disc number on the album that this track is from.
  • xesam:firstUsed (Date/Time): When the track was first played.
  • xesam:genre (List of Strings): The genre(s) of the track.
  • xesam:lastUsed (Date/Time): When the track was last played.
  • xesam:lyricist (List of Strings): The lyricist(s) of the track.
  • xesam:title (String): The track title.
  • xesam:trackNumber (Integer): The track number on the album disc.
  • xesam:url (URI): The location of the media file.
  • xesam:useCount (Integer): The number of times the track has been played.
  • xesam:userRating (Float): A user-specified rating. This should be in the range 0.0 to 1.0.

MacOS:

  • albumArtist: String?: The primary performing artist for an album.
  • albumArtistPersistentID: MPMediaEntityPersistentID: The persistent identifier for the primary performing artist for an album.
  • albumPersistentID: MPMediaEntityPersistentID: The persistent identifier for an album.
  • albumTitle: String?: The title of an album, such as Live on Mars, rather than the title of an individual song on the album, such as “Crater Dance.”
  • albumTrackCount: Int: The number of tracks for the album that contains the media item.
  • albumTrackNumber: Int: The track number of the media item, for a media item that is part of an album.
  • artist: String?: The performing artists for a media item — which may vary from the primary artist for the album that a media item belongs to.
  • artistPersistentID: MPMediaEntityPersistentID: The persistent identifier for an artist.
  • artwork: MPMediaItemArtwork?: The artwork image for the media item.
  • assetURL: URL?: The URL that points to the media item.
  • beatsPerMinute: Int: The number of musical beats per minute for the media item.
  • bookmarkTime: TimeInterval: The time of the user’s most recent interaction with the bookmark in the media item.
  • isCloudItem: Bool: A Boolean value that indicates whether the media item is an iCloud Music Library item.
  • comments: String?: Textual information about the media item.
  • isCompilation: Bool: A Boolean value that indicates whether the media item is part of a compilation.
  • isPreorder: Bool: A Boolean value that indicates whether the media item is a preorder.
  • composer: String?: The musical composer for the media item.
  • composerPersistentID: MPMediaEntityPersistentID: The persistent identifier for a composer.
  • dateAdded: Date: The date the user adds the media item to the library.
  • discCount: Int: The number of discs for the album that contains the media item.
  • discNumber: Int: The disc number of the media item, for a media item that is part of a multidisc album.
  • isExplicitItem: Bool: A Boolean value that indicates whether the media item has explicit (adult) lyrics or language.
  • genre: String?: The music or film genre of the media item.
  • genrePersistentID: MPMediaEntityPersistentID: The persistent identifier for a genre.
  • lastPlayedDate: Date?: The most recent play date of the media item.
  • lyrics: String?: The lyrics for the media item.
  • mediaType: MPMediaType: The media type of the media item.
  • persistentID: MPMediaEntityPersistentID: The persistent identifier for the media item.
  • playCount: Int: The number of times the user plays the media item.
  • playbackDuration: TimeInterval: The playback duration of the media item.
  • playbackStoreID: String: The nonlibrary identifier for a media item.
  • podcastPersistentID: MPMediaEntityPersistentID: The persistent identifier for an audio podcast.
  • podcastTitle: String?: The title of a podcast, such as This Martian Drudgery, rather than the title of an individual episode of a podcast, such as “Episode 12: Another Cold Day at the Pole.”
  • hasProtectedAsset: Bool: A Boolean value that indicates whether the media item has a protected asset.
  • rating: Int: The user-specified rating of the media item.
  • releaseDate: Date?: The date of the media item’s first public release.
  • skipCount: Int: The number of times the user skips playing the media item.
  • title: String?: The title or name of the media item.
  • userGrouping: String?: Grouping information for the media item.

Windows:

  • Type: Defines values for the types of media playback (Unknown, Music, Video and Image).
  • Thumbnail: Gets or sets thumbnail image associated with the currently playing media.
  • AppMediaId: Gets or sets the media id of the app.
  • MusicProperties:
    • AlbumArtist: Gets or sets the name of the album artist.
    • AlbumTitle: Gets or sets the album title.
    • AlbumTrackCount: Gets or sets the album track count.
    • Artist: Gets or set the name of the song artist.
    • Genres: Gets a modifiable list of strings representing genre names.
    • Title: Gets or set the title of the song.
    • TrackNumber: Gets or sets the track number.
  • VideoProperties:
    • Genres: Gets a modifiable list of strings representing genre names.
    • Subtitle: Gets or sets the subtitle of the video.
    • Title: Gets or sets the title of the video.
  • ImageProperties:
    • Subtitle: Gets or sets the subtitle of the image.
    • Title: Gets or sets the title of the image.

MacOS support

Hi! I began working on the Mac support and I hope I'll have something to show in a couple of days. I was wondering if there's any specific reason for the MediaControls facade in lib.rs, in other words why are the platform-specific MediaControls structs not exposed directly, avoiding the need for an ext trait? Thanks!

Miscompile when cross-compiling to Windows from Linux using MinGW-w64

Compiling to x86_64-pc-windows-gnu on Linux using the MinGW-w64 toolchain produces this lovely error with -Z macro-backtrace

error: couldn't read /home/beinsezii/Rust/ompl/target/x86_64-pc-windows-gnu/release/build/souvlaki-0ea84c42cbb27be4/out/windows.rs: No such file or directory (os error 2)
    --> /home/beinsezii/.cargo/registry/src/github.com-1ecc6299db9ec823/windows-0.17.2/src/macros.rs:5:9
     |
3    | / macro_rules! include_bindings {
4    | |     () => {
5    | |         ::std::include!(::std::concat!(::std::env!("OUT_DIR"), "/windows.rs"));
     | |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation (#2)
6    | |     };
7    | | }
     | |_- in this expansion of `::windows::include_bindings!` (#1)
     |
    ::: /home/beinsezii/.cargo/registry/src/github.com-1ecc6299db9ec823/souvlaki-0.4.4/src/platform/windows/mod.rs:4:5
     |
4    |       ::windows::include_bindings!();
     |       ------------------------------ in this macro invocation (#1)

Seems it's part of windows-rs 638 and 1128. Given that these have had progress dating as recent as September, and souvlaki uses a windows-rs version from July, it's possible that updating the windows-rs dependency will resolve or at least progress the issue.

I would try this myself, but windows-rs has changed it's structure so much since I'm not sure what the current equivalent of things like windows::factory are.

Documentation for everything

Right now there is zero documentation for souvlaki asides from the example. These are the things I'd like to do about it:

  • Add a short code snippet in the README showing the basic functionality.
  • Make the docs work properly with the platforms. Right now you can't access windows docs from docs.rs. Supposedly, the docs are enabled for Linux, macOS and Windows. The docs should be updated on the next crates.io version.
  • Add docstrings in the source code.

Linux: Volume control

Currently as of latest commit f169034 at the time of this issue:

#[dbus_interface(property)]
fn volume(&self) -> f64 {
1.0
}

It seems that volume requests are simply not handled and is simply a stub function(?)

When is this feature going to be implemented and picked up by MPRIS requests?

Thanks.

Fetching media playback metadata?

Is there currently a way to fetch the metadata of a currently playing media item? If not, would it be in the scope of this crate to implement it?

MPRIS support

I've been trying for a few months to get MPRIS working now with mpris-player, but I haven't got it working yet. Getting this feature working is a priority since it means the library will be truly cross-platform.

After looking at the spotifyd source code, maybe we can take the same approach and just implement our own MPRIS player from scratch using D-Bus. That won't be easy though.

MacOS: Volume control

Currently I have no way to implement this. I plan on (Q1 2024) buying a Macbook. There I will be able to implement some missing features.

Windows hangs on `MediaControls::set_metadata()`

Hello, first of all - thanks for this library.

I have some code interacting with souvlaki, based off one of the examples to create a dummy window.

I'm encountering some issues on Windows 10 Pro 10.0.19045 Build 19045.

This line inside of MediaControls::set_metadata() will hang forever:

let results = loader.get()?;

Calling pump_event_queue() has not been necessary in my case on other machines for quite a while, but I added it back just in-case, however MediaControls::set_metadata() still hangs.

The file:// URL passed is a normal PATH to an image file.

This code does work on some other Windows 10/11 I have though, so I'm not sure what's going on.

Add MSRV (Minimal Supported Rust Version)

Currently this package does not have a mentioned MSRV, neither in the README, rust-toolchain file or Cargo.toml.

Why? because upgrading to 0.7 has lead to the following error for rust versions below 1.74(.1):

error[E0446]: private type `ServiceState` in public interface
  --> /home/hasezoey/.local/cargo/registry/src/index.crates.io-6f17d22bba15001f/souvlaki-0.7.0/src/platform/mpris/dbus/interfaces.rs:19:1
   |
19 | / pub fn register_methods<F>(
20 | |     state: &Arc<Mutex<ServiceState>>,
21 | |     event_handler: &Arc<Mutex<F>>,
22 | |     friendly_name: String,
...  |
25 | | where
26 | |     F: Fn(MediaControlEvent) + Send + 'static,
   | |______________________________________________^ can't leak private type
   |
  ::: /home/hasezoey/.local/cargo/registry/src/index.crates.io-6f17d22bba15001f/souvlaki-0.7.0/src/platform/mpris/dbus/mod.rs:54:1
   |
54 |   struct ServiceState {
   |   ------------------- `ServiceState` declared as private

For more information about this error, try `rustc --explain E0446`.

suggestions:

  • add field package.rust-version to Cargo.toml
  • mention in README
  • add ci test to verify it will build on the msrv

`dbus-crossroads` backend does not immediately apply updates

The best way I can describe this is that frequent updates to the state will get queued but not actually applied until it's read

So for example, if you run an application containing something like

loop {
  controls.set_playback(MediaPlayback::Playing {progress: position});
  thread::sleep(Duration::from_secs(1));
}

for 10 seconds, then call watch -t 1 playerctl position, you'd expect it to look like
11.0
12.0
13.0
but what you actually get is
1.0
2.0
3.0

And if you shorten the watch duration to a smaller poll rate watch -t 0.5 playerctl position, you'd expect
11.0
11.0
12.0
12.0
because the app updates every second, but in reality it's
1.0
2.0
3.0
4.0
again, as it's working through a bunch of 'queued' updates I guess.

As a bonus kicker, the timeout is either extremely long or nonexistent as I can call playerctl position minutes after playback has stopped and it'll still have dozens or hundreds of updates 'queued' before I can read its true state.

The use_zbus backend does not demonstrate this issue, and shows accurate states even with hundreds of metadata updates a second.

I have basically no knowledge of how D-Bus operates, so if I can provide more information let me know.

  • MPRIS controller: playerctld 2.4.1
  • System dbus: dbus 1.14.10
  • Souvlaki: souvlaki = "0.7.3"

[Meta] Git tags and / or a changelog file?

I was browsing through to update to 0.7, but wanted to know the changes but noticed there is no easy way like a CHANGELOG file or git tags or github releases, this issue is to ask for them to be added (git tags should be the easiest).

  • git tags
  • github releases
  • changelog file

Using the library without `winit::event_loop`

Hi, I'm trying to use this library in MacOS Monterey 12.3.1.

Running examples/print_events doesn't work (no media event printed even when I press one, no metadata shown up in the media control). Running example/example.rs works perfectly but I'm not sure I understand the requirement of winit library here. winit and winit::event_loop seem to be the only difference between examples/print_events.rs and examples/example.rs

Does souvlaki work without winit, as I see that winit is only included as the devdependencies? Am I missing something here?

Polling model

Hi! What do you think about switching from the polling model to a callback approach? In my app I don't have any event polling to piggyback at, and passing a impl Fn(...) + Send + 'static would work best. Event queue is easily implementable on top, outside of souvlaki.

Metadata not updating (linux)

First of all I would like to apologize as I am new to reporting issues and this may be bad.
My repository where my code is located is: https://github.com/jernejmarcic/RustyPlayer

My metadata does not update (it only shows the metadata from the first song after the program is ran), I used the code straight from examples and pasted it in, when I had my music logic all in a loop it worked, however now it no longer works.

Old working code

Working code example, I apologize if I uploaded more than was needed:

pub(crate) fn play_random_song(music_list: &[String], debug_mode: bool /*, config_path: &PathBuf*/) -> std::io::Result<()> {
    if music_list.is_empty() {
        println!("No songs found in the specified directory.");
        return Ok(());
    }

    let mut rng = rand::thread_rng();
    let mut played_songs:Vec<usize> = Vec::new();

//    let mut last_paused_state = IS_PAUSED.load(Ordering::SeqCst);

    loop {
 //       let current_paused_state = IS_PAUSED.load(Ordering::SeqCst);
        let randint = rng.gen_range(0..music_list.len());
        // Your actual logic goes here.
        played_songs.push(randint);  // Track played songs
        if debug_mode {println!("Playing song number: {}",randint)}
        if debug_mode {println!("Song numbers played: {:?}", played_songs)}
        if debug_mode{ println!("Playing song file: {}", music_list[randint]);}
        // println!("Played songs index: {:?}", played_songs);
        // Get an output stream handle to the default physical sound device
        let (_stream, stream_handle) = OutputStream::try_default().unwrap();
        let file = BufReader::new(File::open(&music_list[randint]).unwrap());
        let sink = Sink::try_new(&stream_handle).unwrap();
        let tag = Tag::new().read_from_path(&music_list[randint]).unwrap();
        let title = tag.title().unwrap_or_else(|| "Unknown".into());
 //       let album_cover = tag.album_cover();
        let duration_seconds: Option<f64> = tag.duration();  // Example duration in seconds
        let duration: Option<Duration> = convert_to_duration(duration_seconds);
        let artists = tag
            .artists()
            .map(|a| a.join(", "))
            .unwrap_or_else(|| "Unknown".into());
        let album = tag.album_title().unwrap_or_else(|| "Unknown".into());


// Add a dummy source of the sake of the example.
        let source = Decoder::new(file).unwrap();
        sink.append(source);


        // Construct the path to the directory where the .jpg files are located



        let cover_output_path = format!("/tmp/{}.jpg", album);
        let cover_output_path_clone = cover_output_path.clone();
        if debug_mode {println!("Cover export path is: {}", cover_output_path)}
        terminal_ui(&music_list, randint, title, album, artists.clone(), debug_mode, cover_output_path_clone);

  //      let played_songs_clone = played_songs.clone();  // Clone played_songs for the closure
        //     let music_list_clone = music_list;

        #[cfg(not(target_os = "windows"))]
            let hwnd = None;

        #[cfg(target_os = "windows")]
            let hwnd = {
            use raw_window_handle::windows::WindowsHandle;

            let handle: WindowsHandle = unimplemented!();
            Some(handle.hwnd)
        };

        let config = PlatformConfig {
            dbus_name: "rustyplayer",
            display_name: "Rusty Player",
            hwnd,
        };

        let mut controls = MediaControls::new(config).unwrap();

        // The closure must be Send and have a static lifetime.
       // let played_songs_clone = Arc::clone(&played_songs);
        controls
            .attach(
                move |event: MediaControlEvent| match event {
                MediaControlEvent::Play => {
                    if debug_mode {println!("{:?} event received via MPRIS",event)}
                    let current_state = IS_PAUSED.load(Ordering::SeqCst);
                    IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state
                },

                MediaControlEvent::Pause => {
                    if debug_mode {println!("{:?} event received via MPRIS",event)}
                    // Logic to pause the music
                    let current_state = IS_PAUSED.load(Ordering::SeqCst);
                    IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state

                },
                MediaControlEvent::Toggle => {
                    if debug_mode {println!("{:?} event received via MPRIS",event)}
                    // Toggle logic here
                    let current_state = IS_PAUSED.load(Ordering::SeqCst);
                    IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state
                },

                MediaControlEvent::Next => {
                    if debug_mode {println!("{:?} event received via MPRIS",event)}
                    // Logic to skip to the next track
                    // You might need to signal your playback loop to move to the next song
                    SHOULD_SKIP.store(true, Ordering::SeqCst);

                },

                MediaControlEvent::Previous => {
                    if debug_mode {println!("{:?} event received via MPRIS",event)}

                    // TODO: Well make it work. LOL
                    // If only it was not so FUCKING HARD.

                },


                // Add more event handlers as needed
                _ => println!("Event received: {:?}. If you see this message contact me I probably just haven't added support yet for it", event),
            })
            .unwrap();


        // Update the media metadata.
        controls
            .set_metadata(MediaMetadata {
                title: Some(title),
                artist: Some(&*artists),
                album: Some(album),
                duration: duration,
                cover_url: Some(&*format!("file://{}", cover_output_path)),
                ..Default::default()
            })
            .unwrap();


        while !sink.empty() && !SHOULD_SKIP.load(Ordering::SeqCst) {
            // Check and handle play/pause state...
            let current_paused_state = IS_PAUSED.load(Ordering::SeqCst);
            if current_paused_state != LAST_PAUSED_STATE.load(Ordering::SeqCst) {
                if current_paused_state {
                    if debug_mode {println!("Attempting to pause")}
                    sink.pause();
                    if debug_mode {println!("Track should be paused")}
                   // println!("Paused");
                } else if !current_paused_state {  // Changed from 'else' to 'else if' to explicitly check the condition
                    if debug_mode {println!("Attempting to resume/play")}
                    sink.play();
                    if debug_mode {println!("Track should be resumed")}
                   // println!("Play");
                }
                // Update the last paused state to the current state
                LAST_PAUSED_STATE.store(current_paused_state, Ordering::SeqCst);
            }


            thread::sleep(Duration::from_millis(50));
        }

        // Check again for skip in case it was set during playback
        if SHOULD_SKIP.load(Ordering::SeqCst) {
            SHOULD_SKIP.store(false, Ordering::SeqCst);  // Reset the flag
            println!("Attempting to skip to the next track...");
            // No need for 'continue;' here as it's the end of the loop
            continue;
        }

        sink.sleep_until_end();


// The sound plays in a separate thread. This call will block the current thread until the sink
// has finished playing all its queued sounds.

    }
}

Now this implementation is lacking as It did not allow me to have support to play the previous song.

New not working code

This is the function of my new code:

fn music_player(music_list: &[String], debug_mode: bool, song_history: &mut Vec<Vec<usize>>, song_index: usize) {
    if debug_mode { println!("Playing song number: {}", song_index) }
    if debug_mode { println!("Song numbers played: {:?}", song_history) }
    if debug_mode { println!("Playing song file: {}", music_list[song_index]); }
    // println!("Played songs index: {:?}", song_history);
    // Get an output stream handle to the default physical sound device
    let (_stream, stream_handle) = OutputStream::try_default().unwrap();
    let file = BufReader::new(File::open(&music_list[song_index]).unwrap());
    let sink = Sink::try_new(&stream_handle).unwrap();

    let tag = Tag::new().read_from_path(&music_list[song_index]).unwrap();
    let title = tag.title().unwrap_or_else(|| "Unknown".into());
    //       let album_cover = tag.album_cover();
    let duration_seconds: Option<f64> = tag.duration();  // Example duration in miliseconds
    let duration: Option<Duration> = convert_to_duration(duration_seconds);
    let artists = tag
        .artists()
        .map(|a| a.join(", "))
        .unwrap_or_else(|| "Unknown".into());
    let album = tag.album_title().unwrap_or_else(|| "Unknown".into());


// Add a dummy source of the sake of the example.
    let source = Decoder::new(file).unwrap();
    sink.append(source);


    // Construct the path to the directory where the .jpg files are located


    let cover_output_path = format!("/tmp/{}-cover-{}.jpg",PACKAGE_NAME, song_index);
    let cover_output_path_clone = cover_output_path.clone();


    if debug_mode { println!("Cover export path is: {}", cover_output_path) }
    terminal_ui(&music_list, song_index, title, album, artists.clone(), debug_mode, cover_output_path_clone);

    #[cfg(not(target_os = "windows"))]
        let hwnd = None;

    #[cfg(target_os = "windows")]
        let hwnd = {
        use raw_window_handle::windows::WindowsHandle;

        let handle: WindowsHandle = unimplemented!();
        Some(handle.hwnd)
    };

    let config = PlatformConfig {
        dbus_name: PACKAGE_NAME,
        display_name: "Rusty Player",
        hwnd,
    };

    let mut controls = MediaControls::new(config).unwrap();

        controls
            .attach(
                move |event: MediaControlEvent| match event {
                    MediaControlEvent::Play => {
                        if debug_mode { println!("{:?} event received via MPRIS", event) }
                        let current_state = IS_PAUSED.load(Ordering::SeqCst);
                        IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state
                    }

                    MediaControlEvent::Pause => {
                        if debug_mode { println!("{:?} event received via MPRIS", event) }
                        // Logic to pause the music
                        let current_state = IS_PAUSED.load(Ordering::SeqCst);
                        IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state
                    }
                    MediaControlEvent::Toggle => {
                        if debug_mode { println!("{:?} event received via MPRIS", event) }
                        // Toggle logic here
                        let current_state = IS_PAUSED.load(Ordering::SeqCst);
                        IS_PAUSED.store(!current_state, Ordering::SeqCst);  // Toggle the state
                    }

                    MediaControlEvent::Next => {
                        if debug_mode { println!("{:?} event received via MPRIS", event) }
                        // Logic to skip to the next track
                        // You might need to signal your playback loop to move to the next song
                        SHOULD_SKIP.store(true, Ordering::SeqCst);
                    }

                    MediaControlEvent::Previous => {
                        if debug_mode { println!("{:?} event received via MPRIS", event) }
                        SHOULD_PLAY_PREVIOUS.store(true, Ordering::SeqCst);

                    }


                    // Add more event handlers as needed
                    _ => println!("Event received: {:?}. If you see this message contact me I probably just haven't added support yet for it", event),
                })
            .unwrap();

    controls
        .set_metadata(MediaMetadata {
            title: Some(title),
            artist: Some(&*artists),
            album: Some(album),
            duration: duration,
            cover_url: Some(&*format!("file://{}", cover_output_path)),
            ..Default::default()
        })
        .unwrap();


    //    println!("TEST, {album}, {title}, {:?}", artists);

    



    while !sink.empty() && !SHOULD_SKIP.load(Ordering::SeqCst) && !SHOULD_PLAY_PREVIOUS.load(Ordering::SeqCst){

        // Check and handle play/pause state...
        let current_paused_state = IS_PAUSED.load(Ordering::SeqCst);
        if current_paused_state != LAST_PAUSED_STATE.load(Ordering::SeqCst) {
            if current_paused_state {
                if debug_mode { println!("Attempting to pause") }
                sink.pause();
                if debug_mode { println!("Track should be paused") }
                // println!("Paused");
            } else if !current_paused_state {  // Changed from 'else' to 'else if' to explicitly check the condition
                if debug_mode { println!("Attempting to resume/play") }
                sink.play();
                if debug_mode { println!("Track should be resumed") }
                // println!("Play");
            }
            // Update the last paused state to the current state
            LAST_PAUSED_STATE.store(current_paused_state, Ordering::SeqCst);
        }


        thread::sleep(Duration::from_millis(50));
    }

// Inside your playback loop
    while !sink.empty() {
        if SHOULD_SKIP.load(Ordering::SeqCst) {
            SHOULD_SKIP.store(false, Ordering::SeqCst);  // Reset the flag
            if debug_mode { println!("Attempting to skip to the next track..."); }
            sink.clear();

            if song_history[1].len() >= 1 {
                let song_index = song_history[1][song_history[1].len()-1];
                song_history[1].pop();
                music_player(music_list, debug_mode,song_history, song_index/*&mut rng*/);

            } else {
                random_passer(music_list, debug_mode,song_history, /*&mut rng*/);
            }
            // Logic to skip to the next track, adjust `current_index` as needed
            random_passer(music_list, debug_mode,song_history, /*&mut rng*/);
        }

        if SHOULD_PLAY_PREVIOUS.load(Ordering::SeqCst) {
            SHOULD_PLAY_PREVIOUS.store(false, Ordering::SeqCst);  // Reset the flag
            if debug_mode { println!("Attempting to go back to the previous track..."); }

            // Logic to play the previous track, adjust `current_index` as needed
            if song_history[0].len() >= 2 {
                sink.clear();
                song_history[1].push(song_index);
                let song_index = song_history[0][song_history[0].len()-2];
                song_history[0].pop();
                music_player(music_list, debug_mode,song_history, song_index/*&mut rng*/);
            } else {
                println!("Not enough songs in the play queue")
            }



        }

        // Continue existing play/pause state checks here...
        thread::sleep(Duration::from_millis(50));
    }



    sink.sleep_until_end();
    if song_history[1].len() >= 1 {
        let song_index = song_history[1][song_history[1].len()-1];
        song_history[1].pop();
        music_player(music_list, debug_mode,song_history, song_index/*&mut rng*/);

    } else {
        random_passer(music_list, debug_mode,song_history, /*&mut rng*/);
    }
    // Logic to skip to the next track, adjust `current_index` as needed

// The sound plays in a separate thread. This call will block the current thread until the sink
// has finished playing all its queued sounds.
}

This code fixes my issue and allows me to traverse my song queue however when I ran it I notices that my metadata is no longer updated

How I tried fixing the issue:

First of all I made sure my variables (title, artist, album_url, duration) were being updated and they were.
I tried-making sure metadata is ran so I put it in a loop repeating several times, I tried putting it above the mediakey listener but that just broke it, I tried putting the varaible (let mut controls = MediaControls::new(config).unwrap();) in a loop several times and it did not worked.
As of now no solutions that I thought of have worked.

I would like to thank you for all the help in advance :)

Better error handling

unwrap is used a lot throughout the code at this moment. And the platform errors don't implement std::error::Error. Both seem to be easy fixes.

Control capabilities

Right now the MediaControls struct assumes all the events can be handled. This should be under the control of the user, and dynamically, over the course of the program. I think the solution would be to create a struct like this:

// Capability? Ability? 
// I can't think of something else
// It needs a shorter name that's still intuitive.
enum MediaControlCapability {
	Play,
	Pause,
	Toggle,
	Seek,
	// etc...
}

This enum would be used to control the capabilities of the controls, like controls.set_enabled(MediaControlCapability::Play, true);. The implementation would probably vary in each platform.

If a capability is disabled, should we, still delegate it to the user handler? We probably shouldn't since in moost cases behaviour the specs say it should be ignored. Besides, it would be annoying having to check capabilities in the handler again.

Another feature I would like to implement with this issue is allowing players without controls, such as a player only showing metadata, handling no events.

Album art support

This feature is also crucial to souvlaki, especially on Windows, where the album art is displayed on the screen when pressing any media key.

Media not detected in KDE media player

When I use https://github.com/jpochyla/psst on Manjaro KDE, there is no thumbnail and the play/pause button is on 'play' (and doesn't work) although music is playing. However, the previous/next buttons work.

image

In the taskbar context menu, the stop button is greyed out and the play/pause button doesn't work either.

image

I don't know if it's a bug coming from this module or from psst implementation.


Manjaro Linux 21.1.3, KDE 5.22.5
Ref: jpochyla/psst#185

PropertiesChanged signal on wrong interface

As the Metadata as well as the PlaybackStatus properties are both properties on the org.mpris.MediaPlayer2.Player interface, the emitted PropertiesChanged signal should be also for this interface.

Instead, it uses org.mpris.MediaPlayer2:

let properties_changed = PropertiesPropertiesChanged {
interface_name: "org.mpris.MediaPlayer2".to_owned(),
changed_properties,
invalidated_properties: Vec::new(),
};

See also the output of dbus-monitor:

# dbus-monitor --session "sender=org.mpris.MediaPlayer2.psst,type=signal"    
signal time=1642344094.765597 sender=org.freedesktop.DBus -> destination=:1.255 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
   string ":1.255"
signal time=1642344094.765628 sender=org.freedesktop.DBus -> destination=:1.255 serial=4 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameLost
   string ":1.255"
signal time=1642344097.536915 sender=:1.202 -> destination=(null destination) serial=50 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
   string "org.mpris.MediaPlayer2"
   array [
      dict entry(
         string "PlaybackStatus"
         variant             string "Paused"
      )
   ]
   array [
   ]
signal time=1642344098.885224 sender=:1.202 -> destination=(null destination) serial=52 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
   string "org.mpris.MediaPlayer2"
   array [
      dict entry(
         string "PlaybackStatus"
         variant             string "Playing"
      )
   ]
   array [
   ]

Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.