Compare commits
5 commits
8dc40ac3fb
...
7f20e422d7
Author | SHA1 | Date | |
---|---|---|---|
7f20e422d7 | |||
a8776faa1f | |||
7279532745 | |||
83e627d3d2 | |||
472770013e |
8 changed files with 290 additions and 67 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
@ -38,6 +38,21 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -204,6 +219,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
|
"chrono",
|
||||||
"gettext-rs",
|
"gettext-rs",
|
||||||
"glib-build-tools",
|
"glib-build-tools",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
@ -397,6 +413,21 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -1180,6 +1211,29 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -2577,6 +2631,15 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2889,6 +2952,7 @@ version = "5.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c690a1da8858fd4377b8cc3134a753b0bea1d8ebd78ad6e5897fab821c5e184e"
|
checksum = "c690a1da8858fd4377b8cc3134a753b0bea1d8ebd78ad6e5897fab821c5e184e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"endi",
|
"endi",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adw = { version = "0.7.0", package = "libadwaita", features = ["v1_6"] }
|
adw = { version = "0.7.0", package = "libadwaita", features = ["v1_6"] }
|
||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
gettext-rs = { version = "0.7.2", features = ["gettext-system"] }
|
gettext-rs = { version = "0.7.2", features = ["gettext-system"] }
|
||||||
gtk = { version = "0.9.2", package = "gtk4", features = ["v4_16"] }
|
gtk = { version = "0.9.2", package = "gtk4", features = ["v4_16"] }
|
||||||
oo7 = "0.3.3"
|
oo7 = "0.3.3"
|
||||||
|
@ -14,7 +15,7 @@ reqwest = { version = "0.12.9", features = ["json"] }
|
||||||
serde = { version = "1.0.214", features = ["derive"] }
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
zbus = "5.0.1"
|
zbus = { version = "5.0.1", features = ["chrono"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.70.1"
|
bindgen = "0.70.1"
|
||||||
|
|
|
@ -8,30 +8,88 @@ const MICROSECONDS: f64 = 1e6; // in a second
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MetadataMap {
|
struct MetadataMap {
|
||||||
pub track_id: Option<OwnedObjectPath>,
|
// mpris
|
||||||
// rest: TODO
|
track_id: Option<OwnedObjectPath>,
|
||||||
|
length: Option<i64>,
|
||||||
|
//art_url: Option<url::Url>,
|
||||||
|
// xesam
|
||||||
|
album: Option<String>,
|
||||||
|
//album_artist: Option<Vec<String>>,
|
||||||
|
artist: Option<Vec<String>>,
|
||||||
|
//as_text: Option<String>,
|
||||||
|
//audio_bpm: Option<i32>,
|
||||||
|
//auto_rating: Option<f32>,
|
||||||
|
//comment: Option<Vec<String>>,
|
||||||
|
//composer: Option<Vec<String>>,
|
||||||
|
content_created: Option<chrono::NaiveDateTime>,
|
||||||
|
//disc_number: Option<i32>,
|
||||||
|
//first_used: Option<chrono::DateTime>,
|
||||||
|
genre: Option<Vec<String>>,
|
||||||
|
//last_used: Option<chrono::DateTime>,
|
||||||
|
//lyricist: Option<Vec<String>>,
|
||||||
|
title: Option<String>,
|
||||||
|
track_number: Option<i32>,
|
||||||
|
//url: Option<String>,
|
||||||
|
//use_count: Option<String>,
|
||||||
|
user_rating: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetadataMap {
|
impl MetadataMap {
|
||||||
fn from_playbin_song(song: Option<&crate::playbin::Song>) -> Self {
|
fn from_playbin_song(song: Option<&crate::playbin::Song>) -> Self {
|
||||||
song.map(|song| MetadataMap {
|
song.map(|song| MetadataMap {
|
||||||
// FIXME: see if there's a better way to do this lol
|
// use a unique growing counter to identify tracks
|
||||||
track_id: Some({
|
track_id: Some({
|
||||||
let song_object_intptr =
|
format!("/eu/callcc/audrey/Track/{}", song.counter())
|
||||||
glib::translate::ToGlibPtr::<*const _>::to_glib_none(song).0 as usize;
|
|
||||||
format!("/eu/callcc/audrey/TrackId/{song_object_intptr:x}")
|
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}),
|
}),
|
||||||
|
length: Some(song.duration() * MICROSECONDS as i64),
|
||||||
|
album: Some(song.album().into()),
|
||||||
|
artist: Some(vec![song.artist().into()]),
|
||||||
|
//content_created: song.year().map(|year| chrono::NaiveDate::from_yo_opt(year, 1).unwrap()), // FIXME: replace this unwrap with Some(Err) -> None
|
||||||
|
//genre: Some(song.genre.iter().collect()),
|
||||||
|
title: Some(song.title().into()),
|
||||||
|
//track_number: song.track().map(|u| u as i32),
|
||||||
|
//user_rating: Some(if song.starred().is_none() { 0.0 } else { 1.0 }),
|
||||||
|
..Default::default()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(Default::default)
|
.unwrap_or_else(Default::default)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_hash_map(&self) -> HashMap<&'static str, Value> {
|
fn as_hash_map(&self) -> HashMap<&'static str, Value> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
if let Some(track_id) = &self.track_id {
|
if let Some(track_id) = &self.track_id {
|
||||||
map.insert("mpris:trackid", Value::new(track_id.as_ref()));
|
map.insert("mpris:trackid", Value::new(track_id.as_ref()));
|
||||||
}
|
}
|
||||||
|
if let Some(length) = &self.length {
|
||||||
|
map.insert("mpris:length", Value::new(length));
|
||||||
|
}
|
||||||
|
if let Some(album) = &self.album {
|
||||||
|
map.insert("xesam:album", Value::new(album));
|
||||||
|
}
|
||||||
|
if let Some(artist) = &self.artist {
|
||||||
|
map.insert("xesam:artist", Value::new(artist));
|
||||||
|
}
|
||||||
|
if let Some(content_created) = &self.content_created {
|
||||||
|
map.insert(
|
||||||
|
"xesam:contentCreated",
|
||||||
|
Value::new(content_created.format("%+").to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(genre) = &self.genre {
|
||||||
|
map.insert("xesam:genre", Value::new(genre));
|
||||||
|
}
|
||||||
|
if let Some(track_number) = self.track_number {
|
||||||
|
map.insert("xesam:trackNumber", Value::new(track_number));
|
||||||
|
}
|
||||||
|
if let Some(title) = &self.title {
|
||||||
|
map.insert("xesam:title", Value::new(title));
|
||||||
|
}
|
||||||
|
if let Some(user_rating) = self.user_rating {
|
||||||
|
map.insert("xesam:userRating", Value::new(user_rating));
|
||||||
|
}
|
||||||
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +97,6 @@ impl MetadataMap {
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
playbin: SendWeakRef<crate::Playbin>,
|
playbin: SendWeakRef<crate::Playbin>,
|
||||||
metadata: MetadataMap,
|
metadata: MetadataMap,
|
||||||
volume: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -52,7 +109,6 @@ impl Player {
|
||||||
let player = Self {
|
let player = Self {
|
||||||
playbin: playbin.downgrade().into(),
|
playbin: playbin.downgrade().into(),
|
||||||
metadata: MetadataMap::from_playbin_song(None),
|
metadata: MetadataMap::from_playbin_song(None),
|
||||||
volume: (playbin.volume() as f64) / 100.0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
object_server.at("/org/mpris/MediaPlayer2", player).await?;
|
object_server.at("/org/mpris/MediaPlayer2", player).await?;
|
||||||
|
@ -83,18 +139,79 @@ impl Player {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
playbin.connect_closure(
|
||||||
|
"seeked",
|
||||||
|
false,
|
||||||
|
glib::closure_local!(
|
||||||
|
#[strong]
|
||||||
|
player_ref,
|
||||||
|
move |_playbin: &crate::Playbin, position: f64| {
|
||||||
|
let player_ref = player_ref.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
player_ref
|
||||||
|
.seeked((position * MICROSECONDS) as i64)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
playbin.connect_notify_local(
|
||||||
|
Some("play-queue-length"),
|
||||||
|
glib::clone!(
|
||||||
|
#[strong]
|
||||||
|
player_ref,
|
||||||
|
move |_playbin: &crate::Playbin, _| {
|
||||||
|
let player_ref = player_ref.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let player = player_ref.get_mut().await;
|
||||||
|
// properties that depend on the play queue length
|
||||||
|
player
|
||||||
|
.can_go_next_changed(player_ref.signal_emitter())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
player
|
||||||
|
.can_go_previous_changed(player_ref.signal_emitter())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
player
|
||||||
|
.can_play_changed(player_ref.signal_emitter())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
playbin.connect_notify_local(
|
||||||
|
Some("state"),
|
||||||
|
glib::clone!(
|
||||||
|
#[strong]
|
||||||
|
player_ref,
|
||||||
|
move |_playbin: &crate::Playbin, _| {
|
||||||
|
let player_ref = player_ref.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let player = player_ref.get_mut().await;
|
||||||
|
// properties that depend on the playbin state
|
||||||
|
player
|
||||||
|
.playback_status_changed(player_ref.signal_emitter())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
playbin.connect_notify_local(
|
playbin.connect_notify_local(
|
||||||
Some("volume"),
|
Some("volume"),
|
||||||
glib::clone!(
|
glib::clone!(
|
||||||
#[strong]
|
#[strong]
|
||||||
player_ref,
|
player_ref,
|
||||||
move |playbin: &crate::Playbin, _| {
|
move |_playbin: &crate::Playbin, _| {
|
||||||
let playbin_volume = playbin.volume();
|
|
||||||
|
|
||||||
let player_ref = player_ref.clone();
|
let player_ref = player_ref.clone();
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
let mut player = player_ref.get_mut().await;
|
let player = player_ref.get_mut().await;
|
||||||
player.volume = (playbin_volume as f64) / 100.0;
|
|
||||||
player
|
player
|
||||||
.volume_changed(player_ref.signal_emitter())
|
.volume_changed(player_ref.signal_emitter())
|
||||||
.await
|
.await
|
||||||
|
@ -119,32 +236,43 @@ impl Player {
|
||||||
let playbin = self.playbin.upgrade().unwrap();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
if playbin.play_queue_position() + 1 > playbin.play_queue_length() {
|
if playbin.play_queue_position() + 1 > playbin.play_queue_length() {
|
||||||
// If there is no next track (and endless playback and track repeat are both off), stop playback.
|
// If there is no next track (and endless playback and track repeat are both off), stop playback.
|
||||||
self.stop();
|
// (interpret this as something else than what Stop does)
|
||||||
|
playbin.stop();
|
||||||
} else {
|
} else {
|
||||||
playbin.go_to_next_track();
|
playbin.go_to_next_track();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn previous(&self) {
|
fn previous(&self) {
|
||||||
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
|
|
||||||
// If CanGoPrevious is false, attempting to call this method should have no effect.
|
// If CanGoPrevious is false, attempting to call this method should have no effect.
|
||||||
if !self.can_go_previous() {
|
if !self.can_go_previous() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let playbin = self.playbin.upgrade().unwrap();
|
|
||||||
if playbin.play_queue_position() == 0 {
|
if playbin.play_queue_position() == 0 {
|
||||||
// If there is no previous track (and endless playback and track repeat are both off), stop playback.
|
// If there is no previous track (and endless playback and track repeat are both off), stop playback.
|
||||||
self.stop();
|
// (interpret this as something else than what Stop does)
|
||||||
|
playbin.stop();
|
||||||
} else {
|
} else {
|
||||||
playbin.go_to_prev_track();
|
playbin.go_to_prev_track();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pause(&self) {
|
fn pause(&self) {
|
||||||
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
|
|
||||||
// If CanPause is false, attempting to call this method should have no effect.
|
// If CanPause is false, attempting to call this method should have no effect.
|
||||||
if !self.can_pause() {
|
if !self.can_pause() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If playback is already paused, this has no effect.
|
||||||
|
if playbin.state() != crate::playbin::State::Playing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.playbin.upgrade().unwrap().pause();
|
self.playbin.upgrade().unwrap().pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,18 +286,31 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) {
|
fn stop(&self) {
|
||||||
// TODO: Calling Play after this should cause playback to start again from the beginning of the track.
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
// (not the play queue!!)
|
|
||||||
self.playbin.upgrade().unwrap().stop();
|
// If playback is already stopped, this has no effect.
|
||||||
|
if playbin.state() != crate::playbin::State::Playing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling Play after this should cause playback to start again from the beginning of the track.
|
||||||
|
playbin.pause();
|
||||||
|
playbin.seek(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play(&self) {
|
fn play(&self) {
|
||||||
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
|
|
||||||
// If CanPlay is false, attempting to call this method should have no effect.
|
// If CanPlay is false, attempting to call this method should have no effect.
|
||||||
if !self.can_play() {
|
if !self.can_play() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let playbin = self.playbin.upgrade().unwrap();
|
// If already playing, this has no effect.
|
||||||
|
if playbin.state() == crate::playbin::State::Playing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If there is no track to play, this has no effect.
|
// If there is no track to play, this has no effect.
|
||||||
if playbin.play_queue_length() == 0 {
|
if playbin.play_queue_length() == 0 {
|
||||||
return;
|
return;
|
||||||
|
@ -247,18 +388,23 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn set_loop_status(&self, _loop_status: &str) {
|
fn set_loop_status(&self, _loop_status: &str) -> zbus::Result<()> {
|
||||||
todo!()
|
Err(zbus::Error::Unsupported) // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn playback_rate(&self) -> f64 {
|
fn rate(&self) -> f64 {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn set_playback_rate(&self, _playback_rate: f64) -> zbus::Result<()> {
|
fn set_rate(&self, rate: f64) {
|
||||||
Err(zbus::Error::Unsupported)
|
// A value of 0.0 should not be set by the client. If it is, the media player should act as though Pause was called.
|
||||||
|
if rate == 0.0 {
|
||||||
|
self.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
// just ignore anything else
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
|
@ -278,11 +424,15 @@ impl Player {
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn volume(&self) -> f64 {
|
fn volume(&self) -> f64 {
|
||||||
1.0 // TODO
|
self.playbin.upgrade().unwrap().volume() as f64 / 100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn set_volume(&mut self, volume: f64) {
|
fn set_volume(&mut self, mut volume: f64) {
|
||||||
|
// When setting, if a negative value is passed, the volume should be set to 0.0.
|
||||||
|
if volume < 0.0 {
|
||||||
|
volume = 0.0;
|
||||||
|
}
|
||||||
let playbin = self.playbin.upgrade().unwrap();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
// FIXME: check if this is set by the notify callback: self.volume = volume;
|
// FIXME: check if this is set by the notify callback: self.volume = volume;
|
||||||
playbin.set_volume((volume * 100.0) as i32);
|
playbin.set_volume((volume * 100.0) as i32);
|
||||||
|
@ -305,27 +455,32 @@ impl Player {
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn can_go_next(&self) -> bool {
|
fn can_go_next(&self) -> bool {
|
||||||
self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped
|
// same as can_play
|
||||||
|
self.playbin.upgrade().unwrap().play_queue_length() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn can_go_previous(&self) -> bool {
|
fn can_go_previous(&self) -> bool {
|
||||||
self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped
|
// same as can_play
|
||||||
|
self.playbin.upgrade().unwrap().play_queue_length() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn can_play(&self) -> bool {
|
fn can_play(&self) -> bool {
|
||||||
self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped
|
// it only makes sense to disallow "play" when the play queue is empty
|
||||||
|
self.playbin.upgrade().unwrap().play_queue_length() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn can_pause(&self) -> bool {
|
fn can_pause(&self) -> bool {
|
||||||
self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped
|
// we don't play anything that can't be paused
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn can_seek(&self) -> bool {
|
fn can_seek(&self) -> bool {
|
||||||
self.playbin.upgrade().unwrap().state() != crate::playbin::State::Stopped
|
// we don't play anything that can't be seeked
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[zbus(property(emits_changed_signal = "const"))]
|
#[zbus(property(emits_changed_signal = "const"))]
|
||||||
|
|
|
@ -10,6 +10,9 @@ private struct Audrey.CommandCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Audrey.PlaybinSong : Object {
|
public class Audrey.PlaybinSong : Object {
|
||||||
|
private static int64 next_counter = 0;
|
||||||
|
public int64 counter { get; private set; }
|
||||||
|
|
||||||
private Subsonic.Song inner;
|
private Subsonic.Song inner;
|
||||||
public string id { get { return inner.id; } }
|
public string id { get { return inner.id; } }
|
||||||
public string title { get { return inner.title; } }
|
public string title { get { return inner.title; } }
|
||||||
|
@ -27,6 +30,9 @@ public class Audrey.PlaybinSong : Object {
|
||||||
public PlaybinSong (Subsonic.Client api, Subsonic.Song song) {
|
public PlaybinSong (Subsonic.Client api, Subsonic.Song song) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.inner = song;
|
this.inner = song;
|
||||||
|
|
||||||
|
this.counter = next_counter;
|
||||||
|
next_counter += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Subsonic.Client api;
|
private Subsonic.Client api;
|
||||||
|
|
|
@ -13,6 +13,9 @@ pub mod ffi {
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn audrey_playbin_song_get_type() -> glib::ffi::GType;
|
pub fn audrey_playbin_song_get_type() -> glib::ffi::GType;
|
||||||
|
pub fn audrey_playbin_song_get_counter(self_: *mut AudreyPlaybinSong) -> i64;
|
||||||
|
pub fn audrey_playbin_song_get_id(self_: *mut AudreyPlaybinSong)
|
||||||
|
-> *const std::ffi::c_char;
|
||||||
pub fn audrey_playbin_song_get_title(
|
pub fn audrey_playbin_song_get_title(
|
||||||
self_: *mut AudreyPlaybinSong,
|
self_: *mut AudreyPlaybinSong,
|
||||||
) -> *const std::ffi::c_char;
|
) -> *const std::ffi::c_char;
|
||||||
|
@ -22,6 +25,7 @@ pub mod ffi {
|
||||||
pub fn audrey_playbin_song_get_album(
|
pub fn audrey_playbin_song_get_album(
|
||||||
self_: *mut AudreyPlaybinSong,
|
self_: *mut AudreyPlaybinSong,
|
||||||
) -> *const std::ffi::c_char;
|
) -> *const std::ffi::c_char;
|
||||||
|
pub fn audrey_playbin_song_get_duration(self_: *mut AudreyPlaybinSong) -> i64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +42,14 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
|
pub fn counter(&self) -> i64 {
|
||||||
|
unsafe { ffi::audrey_playbin_song_get_counter(self.to_glib_none().0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> GString {
|
||||||
|
unsafe { from_glib_none(ffi::audrey_playbin_song_get_id(self.to_glib_none().0)) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> GString {
|
pub fn title(&self) -> GString {
|
||||||
unsafe { from_glib_none(ffi::audrey_playbin_song_get_title(self.to_glib_none().0)) }
|
unsafe { from_glib_none(ffi::audrey_playbin_song_get_title(self.to_glib_none().0)) }
|
||||||
}
|
}
|
||||||
|
@ -49,4 +61,8 @@ impl Song {
|
||||||
pub fn album(&self) -> GString {
|
pub fn album(&self) -> GString {
|
||||||
unsafe { from_glib_none(ffi::audrey_playbin_song_get_album(self.to_glib_none().0)) }
|
unsafe { from_glib_none(ffi::audrey_playbin_song_get_album(self.to_glib_none().0)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn duration(&self) -> i64 {
|
||||||
|
unsafe { ffi::audrey_playbin_song_get_duration(self.to_glib_none().0) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ pub struct Child {
|
||||||
pub artist: String,
|
pub artist: String,
|
||||||
pub track: Option<u32>,
|
pub track: Option<u32>,
|
||||||
pub year: Option<u32>,
|
pub year: Option<u32>,
|
||||||
pub starred: Option<()>,
|
pub starred: Option<chrono::DateTime<chrono::offset::Utc>>, // TODO: check which is best
|
||||||
|
// applicable
|
||||||
pub duration: u64,
|
pub duration: u64,
|
||||||
pub play_count: Option<u32>,
|
pub play_count: Option<u32>,
|
||||||
pub genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod imp {
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
use glib::{gformat, GString};
|
use glib::{gformat, GString, WeakRef};
|
||||||
use gtk::{gdk, glib};
|
use gtk::{gdk, glib};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ mod imp {
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
playbin: RefCell<Option<crate::Playbin>>, // TODO: weak
|
playbin: WeakRef<crate::Playbin>,
|
||||||
#[property(get, set, default = true)]
|
#[property(get, set, default = true)]
|
||||||
show_cover_art: Cell<bool>,
|
show_cover_art: Cell<bool>,
|
||||||
|
|
||||||
#[property(type = i32, get = Self::volume, set = Self::set_volume)]
|
#[property(get, set)]
|
||||||
_volume: (),
|
volume: Cell<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -102,8 +102,7 @@ mod imp {
|
||||||
value: f64,
|
value: f64,
|
||||||
range: >k::Range,
|
range: >k::Range,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
if range.adjustment().lower() < range.adjustment().upper() {
|
if range.adjustment().lower() < range.adjustment().upper() {
|
||||||
playbin.seek(value);
|
playbin.seek(value);
|
||||||
}
|
}
|
||||||
|
@ -112,22 +111,19 @@ mod imp {
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_skip_forward_clicked(&self) {
|
fn on_skip_forward_clicked(&self) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
playbin.go_to_next_track();
|
playbin.go_to_next_track();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_skip_backward_clicked(&self) {
|
fn on_skip_backward_clicked(&self) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
playbin.go_to_prev_track();
|
playbin.go_to_prev_track();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn seek_backward(&self) {
|
fn seek_backward(&self) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
// 10 seconds
|
// 10 seconds
|
||||||
let mut new_position = playbin.position() - 10.0;
|
let mut new_position = playbin.position() - 10.0;
|
||||||
if new_position < 0.0 {
|
if new_position < 0.0 {
|
||||||
|
@ -138,8 +134,7 @@ mod imp {
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn seek_forward(&self) {
|
fn seek_forward(&self) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
// 10 seconds
|
// 10 seconds
|
||||||
let mut new_position = playbin.position() + 10.0;
|
let mut new_position = playbin.position() + 10.0;
|
||||||
if new_position > playbin.duration() {
|
if new_position > playbin.duration() {
|
||||||
|
@ -150,8 +145,7 @@ mod imp {
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_play_pause_clicked(&self, _button: >k::Button) {
|
fn on_play_pause_clicked(&self, _button: >k::Button) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
|
|
||||||
if playbin.state() == crate::playbin::State::Playing {
|
if playbin.state() == crate::playbin::State::Playing {
|
||||||
playbin.pause();
|
playbin.pause();
|
||||||
|
@ -162,25 +156,9 @@ mod imp {
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_mute_toggle(&self) {
|
fn on_mute_toggle(&self) {
|
||||||
let playbin = self.playbin.borrow();
|
let playbin = self.playbin.upgrade().unwrap();
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
playbin.set_mute(!playbin.mute());
|
playbin.set_mute(!playbin.mute());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn volume(&self) -> i32 {
|
|
||||||
let playbin = self.playbin.borrow();
|
|
||||||
|
|
||||||
match playbin.as_ref() {
|
|
||||||
None => 100,
|
|
||||||
Some(playbin) => playbin.volume(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_volume(&self, value: i32) {
|
|
||||||
let playbin = self.playbin.borrow();
|
|
||||||
let playbin = playbin.as_ref().unwrap();
|
|
||||||
playbin.set_volume(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ public class Audrey.Ui.Window : Adw.ApplicationWindow {
|
||||||
|
|
||||||
public Window (Gtk.Application app) {
|
public Window (Gtk.Application app) {
|
||||||
Object (application: app);
|
Object (application: app);
|
||||||
|
|
||||||
|
this.playbin.bind_property("volume", this.playbar, "volume", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void now_playing (PlaybinSong song) {
|
private void now_playing (PlaybinSong song) {
|
||||||
|
|
Loading…
Reference in a new issue