sinono3 / souvlaki Goto Github PK
View Code? Open in Web Editor NEWA cross-platform library for handling OS media controls and metadata.
License: MIT License
A cross-platform library for handling OS media controls and metadata.
License: MIT License
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?
I have just tried implementing this, and noticed that the documentation says:
Lines 45 to 46 in fc88dfb
Note that the volume may be higher than 1.0, although generally clients should not attempt to set it above 1.0.
example: playerctl volume 100
will give float 100.0
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)
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!
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
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.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!
Why do you require a *mut c_void
then convert it to HWND
internally, when every windows library directly gives you HWND
.
https://github.com/Sinono3/souvlaki/blob/master/src/platform/windows/mod.rs#L53
How do you even convert HWND
to *mut c_void
? None of the examples even show using the library with any windows crate.
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.
There aren't any screenshots or gifs of souvlaki working on macOS yet 🥲
Right now there is zero documentation for souvlaki asides from the example. These are the things I'd like to do about it:
The feature on windows is called taskbar extensions https://docs.microsoft.com/en-us/windows/win32/shell/taskbar-extensions
Currently as of latest commit f169034 at the time of this issue:
souvlaki/src/platform/mpris/zbus.rs
Lines 308 to 311 in f169034
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.
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?
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.
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.
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:
souvlaki/src/platform/windows/mod.rs
Line 188 in 384539f
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.
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:
package.rust-version
to Cargo.tomlThe 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.
playerctld 2.4.1
dbus 1.14.10
souvlaki = "0.7.3"
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).
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?
it would be great if there would be some documentation about MediaMetadata::cover_url
, like what format it should be in, or if there is anything platform specific (and if needed what formats are supported, like recommending jpg?)
this makes just putting it into another struct or referencing it in errors a little annoying
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
.
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.
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.
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
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 :)
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.
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.
This feature is also crucial to souvlaki, especially on Windows, where the album art is displayed on the screen when pressing any media key.
Playback progress, together with total duration, would be probably the last things we miss.
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.
In the taskbar context menu, the stop button is greyed out and the play/pause button doesn't work either.
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
Is it the Greek street-food souvlaki or smth else?
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
:
souvlaki/src/platform/linux/mod.rs
Lines 439 to 443 in 021369c
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.