From 901bf0e871538fc60b4d80def9c0cc579b801dfa Mon Sep 17 00:00:00 2001 From: Tal <83217276+talwat@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:46:47 +0200 Subject: [PATCH] feat: support for mpris (#14) * feat: initial support for mpris * feat: overhaul app flow, making audio server control main thread instead of the ui thread * fix: remove useless extra thread * fix: last touches * fix: call interface with minimalist flag * fix: fix oversight when silencing alsa --- Cargo.lock | 666 +++++++++++++++++++++++++++++++++--- Cargo.toml | 6 +- src/play.rs | 9 +- src/player.rs | 165 ++++++--- src/player/downloader.rs | 6 +- src/player/mpris.rs | 214 ++++++++++++ src/player/ui.rs | 232 ++++++++----- src/player/ui/components.rs | 4 +- 8 files changed, 1102 insertions(+), 200 deletions(-) create mode 100644 src/player/mpris.rs diff --git a/Cargo.lock b/Cargo.lock index 55c4b9c..5096b3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -132,6 +132,149 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -195,6 +338,28 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -221,9 +386,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.22" +version = "1.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "677207f6eaec43fcfd092a718c847fc38aa261d0e19b8ef6797e0ccbe789e738" dependencies = [ "jobserver", "libc", @@ -251,6 +416,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clang-sys" version = "1.8.1" @@ -264,9 +435,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -274,9 +445,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -318,6 +489,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -377,6 +557,21 @@ dependencies = [ "windows", ] +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.28.1" @@ -385,6 +580,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", + "futures-core", "mio", "parking_lot", "rustix", @@ -402,6 +598,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" version = "0.31.2" @@ -442,6 +648,16 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -478,6 +694,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -494,6 +737,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -552,9 +816,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -567,9 +831,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -577,15 +841,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -594,15 +858,28 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -611,21 +888,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -648,6 +925,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.21" @@ -670,9 +957,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -701,9 +988,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -717,6 +1004,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "html5ever" version = "0.27.0" @@ -767,9 +1066,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" @@ -861,9 +1160,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -871,9 +1170,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -993,6 +1292,7 @@ dependencies = [ "futures", "lazy_static", "libc", + "mpris-server", "rand", "reqwest", "rodio", @@ -1035,6 +1335,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1062,13 +1371,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "log", "wasi", "windows-sys 0.52.0", ] +[[package]] +name = "mpris-server" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058bc2227727af394f34aa51da3e36aeecf2c808f39315d35f754872660750ae" +dependencies = [ + "async-channel", + "futures-channel", + "serde", + "trait-variant", + "zbus", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1121,6 +1443,19 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -1174,9 +1509,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -1206,9 +1541,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" @@ -1254,6 +1589,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1385,12 +1736,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1465,18 +1842,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1486,9 +1863,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1497,15 +1874,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -1597,9 +1974,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "rustls-pki-types", @@ -1754,6 +2131,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1775,6 +2163,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1854,6 +2253,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.7" @@ -2132,9 +2537,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -2144,6 +2561,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -2151,10 +2579,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "unicode-bidi" -version = "0.3.15" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -2567,6 +3012,78 @@ dependencies = [ "memchr", ] +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2593,3 +3110,40 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 765df21..74d1584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ documentation = "https://github.com/talwat/lowfi" homepage = "https://github.com/talwat/lowfi" repository = "https://github.com/talwat/lowfi" +[features] +mpris = ["dep:mpris-server"] + [dependencies] # Basics clap = { version = "4.5.18", features = ["derive", "cargo"] } @@ -37,7 +40,8 @@ bytes = "1.7.2" # Misc scraper = "0.20.0" rodio = { version = "0.19.0", features = ["symphonia-mp3"], default-features = false } -crossterm = "0.28.1" +crossterm = { version = "0.28.1", features = ["event-stream"] } Inflector = "0.11.4" lazy_static = "1.5.0" libc = "0.2.159" +mpris-server = { version = "0.8.1", optional = true } diff --git a/src/play.rs b/src/play.rs index 2dee2dd..447580a 100644 --- a/src/play.rs +++ b/src/play.rs @@ -12,15 +12,14 @@ use crate::Args; /// it when the frontend quits. pub async fn play(args: Args) -> eyre::Result<()> { let (tx, rx) = mpsc::channel(8); + let player = Arc::new(Player::new(!args.alternate).await?); + let ui = task::spawn(ui::start(Arc::clone(&player), tx.clone(), args)); - let player = Arc::new(Player::new(args.alternate).await?); - let audio = task::spawn(Player::play(Arc::clone(&player), tx.clone(), rx)); tx.send(Messages::Init).await?; - ui::start(Arc::clone(&player), tx.clone(), args).await?; - - audio.abort(); + Player::play(Arc::clone(&player), tx.clone(), rx).await?; player.sink.stop(); + ui.abort(); Ok(()) } diff --git a/src/player.rs b/src/player.rs index 67c0498..9fac515 100644 --- a/src/player.rs +++ b/src/player.rs @@ -23,12 +23,20 @@ use crate::tracks::{DecodedTrack, Track, TrackInfo}; pub mod downloader; pub mod ui; +#[cfg(feature = "mpris")] +pub mod mpris; + /// Handles communication between the frontend & audio player. -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum Messages { /// Notifies the audio server that it should update the track. Next, + /// Special in that this isn't sent in a "client to server" sort of way, + /// but rather is sent by a child of the server when a song has not only + /// been requested but also downloaded aswell. + NewSong, + /// This signal is only sent if a track timed out. In that case, /// lowfi will try again and again to retrieve the track. TryAgain, @@ -37,10 +45,13 @@ pub enum Messages { Init, /// Pauses the [Sink]. This will also unpause it if it is paused. - Pause, + PlayPause, /// Change the volume of playback. ChangeVolume(f32), + + /// Quits gracefully. + Quit, } const TIMEOUT: Duration = Duration::from_secs(8); @@ -54,9 +65,14 @@ pub struct Player { pub sink: Sink, /// The [`TrackInfo`] of the current track. - /// This is [`None`] when lowfi is buffering. + /// This is [`None`] when lowfi is buffering/loading. pub current: ArcSwapOption, + /// This is the MPRIS server, which is initialized later on in the + /// user interface. + #[cfg(feature = "mpris")] + pub mpris: tokio::sync::OnceCell>, + /// The tracks, which is a [VecDeque] that holds /// *undecoded* [Track]s. tracks: RwLock>, @@ -105,6 +121,18 @@ impl Player { Ok((stream, handle)) } + /// Just a shorthand for setting `current`. + async fn set_current(&self, info: TrackInfo) -> eyre::Result<()> { + self.current.store(Some(Arc::new(info))); + + Ok(()) + } + + /// A shorthand for checking if `self.current` is [Some]. + pub fn current_exists(&self) -> bool { + self.current.load().is_some() + } + /// Initializes the entire player, including audio devices & sink. /// /// `silent` can control whether alsa's output should be redirected, @@ -133,33 +161,75 @@ impl Player { sink, _handle: handle, _stream, + + #[cfg(feature = "mpris")] + mpris: tokio::sync::OnceCell::new(), }; Ok(player) } - /// Just a shorthand for setting `current`. - async fn set_current(&self, info: TrackInfo) -> eyre::Result<()> { - self.current.store(Some(Arc::new(info))); - - Ok(()) - } - /// This will play the next track, as well as refilling the buffer in the background. - pub async fn next(queue: Arc) -> eyre::Result { - let track = match queue.tracks.write().await.pop_front() { + pub async fn next(&self) -> eyre::Result { + let track = match self.tracks.write().await.pop_front() { Some(x) => x, // If the queue is completely empty, then fallback to simply getting a new track. // This is relevant particularly at the first song. - None => Track::random(&queue.client).await?, + None => Track::random(&self.client).await?, }; let decoded = track.decode()?; - queue.set_current(decoded.info.clone()).await?; Ok(decoded) } + /// This basically just calls [`Player::next`], and then appends the new track to the player. + /// + /// This also notifies the background thread to get to work, and will send `TryAgain` + /// if it fails. This functions purpose is to be called in the background, so that + /// when the audio server recieves a `Next` signal it will still be able to respond to other + /// signals while it's loading. + async fn handle_next( + player: Arc, + itx: Sender<()>, + tx: Sender, + ) -> eyre::Result<()> { + // Serves as an indicator that the queue is "loading". + player.current.store(None); + + player.sink.stop(); + + let track = player.next().await; + + match track { + Ok(track) => { + // Set the current track. + player.set_current(track.info.clone()).await?; + + // Actually start playing it, this is done later so that the amount + // or times where "loading" appears briefly even though the track is + // from the buffer is minimized. + player.sink.append(track.data); + + // Notify the background downloader that there's an empty spot + // in the buffer. + itx.send(()).await?; + + // Notify the audio server that the next song has actually been downloaded. + tx.send(Messages::NewSong).await? + } + Err(error) => { + if !error.downcast::()?.is_timeout() { + tokio::time::sleep(TIMEOUT).await; + } + + tx.send(Messages::TryAgain).await? + } + }; + + Ok(()) + } + /// This is the main "audio server". /// /// `rx` & `tx` are used to communicate with it, for example when to @@ -171,50 +241,48 @@ impl Player { ) -> eyre::Result<()> { // `itx` is used to notify the `Downloader` when it needs to download new tracks. let (downloader, itx) = Downloader::new(player.clone()); - downloader.start().await; + let downloader = downloader.start().await; // Start buffering tracks immediately. itx.send(()).await?; loop { let clone = Arc::clone(&player); - let msg = select! { - Some(x) = rx.recv() => x, + let msg = select! { + biased; + + Some(x) = rx.recv() => x, // This future will finish only at the end of the current track. - Ok(_) = task::spawn_blocking(move || clone.sink.sleep_until_end()) => Messages::Next, + // The condition is a kind-of hack which gets around the quirks + // of `sleep_until_end`. + // + // That's because `sleep_until_end` will return instantly if the sink + // is uninitialized. That's why we put a check to make sure that we're + // only considering it if the sink is empty, but a song is specified by the + // player. + // + // This makes sense since at the end of a song, the sink will be empty, + // but `player.current` still has yet to be cycled. + // + // This is in contrast to a typical `Next` signal, where the sink will + // not be empty. + Ok(_) = task::spawn_blocking(move || clone.sink.sleep_until_end()), + if player.sink.empty() && player.current_exists() => Messages::Next, }; match msg { Messages::Next | Messages::Init | Messages::TryAgain => { - // Skip as early as possible so that music doesn't play - // while lowfi is "loading". - player.sink.stop(); + // This basically just prevents `Next` while a song is still currently loading. + if msg == Messages::Next && !player.current_exists() { + continue; + } - // Serves as an indicator that the queue is "loading". - // This is also set by Player::next. - player.current.store(None); - - let track = Self::next(Arc::clone(&player)).await; - - match track { - Ok(track) => { - player.sink.append(track.data); - - // Notify the background downloader that there's an empty spot - // in the buffer. - itx.send(()).await?; - } - Err(error) => { - if !error.downcast::()?.is_timeout() { - tokio::time::sleep(TIMEOUT).await; - } - - tx.send(Messages::TryAgain).await? - } - }; + // Handle the rest of the signal in the background, + // as to not block the main audio thread. + task::spawn(Self::handle_next(player.clone(), itx.clone(), tx.clone())); } - Messages::Pause => { + Messages::PlayPause => { if player.sink.is_paused() { player.sink.play(); } else { @@ -226,7 +294,16 @@ impl Player { .sink .set_volume((player.sink.volume() + change).clamp(0.0, 1.0)); } + // This basically just continues, but more importantly, it'll re-evaluate + // the select macro at the beginning of the loop. + // See the top section to find out why this matters. + Messages::NewSong => continue, + Messages::Quit => break, } } + + downloader.abort(); + + Ok(()) } } diff --git a/src/player/downloader.rs b/src/player/downloader.rs index ed38ce4..f93ff8a 100644 --- a/src/player/downloader.rs +++ b/src/player/downloader.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use tokio::{ sync::mpsc::{self, Receiver, Sender}, - task, + task::{self, JoinHandle}, }; use crate::tracks::Track; @@ -34,7 +34,7 @@ impl Downloader { } /// Actually starts & consumes the [Downloader]. - pub async fn start(mut self) { + pub async fn start(mut self) -> JoinHandle<()> { task::spawn(async move { // Loop through each update notification. while self.rx.recv().await == Some(()) { @@ -47,6 +47,6 @@ impl Downloader { self.player.tracks.write().await.push_back(track); } } - }); + }) } } diff --git a/src/player/mpris.rs b/src/player/mpris.rs new file mode 100644 index 0000000..420ef08 --- /dev/null +++ b/src/player/mpris.rs @@ -0,0 +1,214 @@ +use std::sync::Arc; + +use mpris_server::{ + zbus::{fdo, Result}, + LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, RootInterface, Time, + TrackId, Volume, +}; +use tokio::sync::mpsc::Sender; + +use super::Messages; + +const ERROR: fdo::Error = fdo::Error::Failed(String::new()); + +/// The actual MPRIS server. +pub struct Player { + pub player: Arc, + pub sender: Sender, +} + +impl RootInterface for Player { + async fn raise(&self) -> fdo::Result<()> { + Err(ERROR) + } + + async fn quit(&self) -> fdo::Result<()> { + self.sender.send(Messages::Quit).await.map_err(|_| ERROR) + } + + async fn can_quit(&self) -> fdo::Result { + Ok(true) + } + + async fn fullscreen(&self) -> fdo::Result { + Ok(false) + } + + async fn set_fullscreen(&self, _: bool) -> Result<()> { + Ok(()) + } + + async fn can_set_fullscreen(&self) -> fdo::Result { + Ok(false) + } + + async fn can_raise(&self) -> fdo::Result { + Ok(false) + } + + async fn has_track_list(&self) -> fdo::Result { + Ok(false) + } + + async fn identity(&self) -> fdo::Result { + Ok("lowfi".to_string()) + } + + async fn desktop_entry(&self) -> fdo::Result { + Ok("dev.talwat.lowfi".to_string()) + } + + async fn supported_uri_schemes(&self) -> fdo::Result> { + Ok(vec!["https".to_string()]) + } + + async fn supported_mime_types(&self) -> fdo::Result> { + Ok(vec!["audio/mpeg".to_string()]) + } +} + +impl PlayerInterface for Player { + async fn next(&self) -> fdo::Result<()> { + self.sender.send(Messages::Next).await.map_err(|_| ERROR) + } + + async fn previous(&self) -> fdo::Result<()> { + Err(ERROR) + } + + async fn pause(&self) -> fdo::Result<()> { + self.sender + .send(Messages::PlayPause) + .await + .map_err(|_| ERROR) + } + + async fn play_pause(&self) -> fdo::Result<()> { + self.sender + .send(Messages::PlayPause) + .await + .map_err(|_| ERROR) + } + + async fn stop(&self) -> fdo::Result<()> { + self.play_pause().await + } + + async fn play(&self) -> fdo::Result<()> { + self.play_pause().await + } + + async fn seek(&self, _offset: Time) -> fdo::Result<()> { + Err(ERROR) + } + + async fn set_position(&self, _track_id: TrackId, _position: Time) -> fdo::Result<()> { + Err(ERROR) + } + + async fn open_uri(&self, _uri: String) -> fdo::Result<()> { + Err(ERROR) + } + + async fn playback_status(&self) -> fdo::Result { + Ok(if !self.player.current_exists() { + PlaybackStatus::Stopped + } else if self.player.sink.is_paused() { + PlaybackStatus::Paused + } else { + PlaybackStatus::Playing + }) + } + + async fn loop_status(&self) -> fdo::Result { + Err(ERROR) + } + + async fn set_loop_status(&self, _loop_status: LoopStatus) -> Result<()> { + Ok(()) + } + + async fn rate(&self) -> fdo::Result { + Ok(self.player.sink.speed().into()) + } + + async fn set_rate(&self, rate: PlaybackRate) -> Result<()> { + self.player.sink.set_speed(rate as f32); + Ok(()) + } + + async fn shuffle(&self) -> fdo::Result { + Ok(true) + } + + async fn set_shuffle(&self, _shuffle: bool) -> Result<()> { + Ok(()) + } + + async fn metadata(&self) -> fdo::Result { + let metadata = match self.player.current.load().as_ref() { + Some(track) => { + let mut metadata = Metadata::builder().title(track.name.clone()).build(); + + metadata.set_length( + track + .duration + .and_then(|x| Some(Time::from_micros(x.as_micros() as i64))), + ); + + metadata + } + None => Metadata::new(), + }; + + Ok(metadata) + } + + async fn volume(&self) -> fdo::Result { + Ok(self.player.sink.volume().into()) + } + + async fn set_volume(&self, volume: Volume) -> Result<()> { + self.player.sink.set_volume(volume as f32); + + Ok(()) + } + + async fn position(&self) -> fdo::Result