mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-28 06:21:23 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f33c48bcc | |||
| 1f48dad71c | |||
| 8066f6d96b | |||
| 5c3bc30a6e | |||
| 90ed05d88d | |||
| f59050ef04 | |||
| bae10edc99 | |||
| cfa2681ba3 | |||
| 03e936ac34 | |||
| f7f96abcbb | |||
| 4d54443593 | |||
| 5afe3dd45b | |||
| bd75ac6190 | |||
| 3e6a8b6e79 | |||
| c1c8deb1b3 | |||
| dc61342af8 | |||
| fded7f7b3f | |||
| ec83680333 | |||
| 16e94e71d3 |
Generated
+93
-61
@@ -327,9 +327,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.51"
|
version = "1.2.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -354,9 +354,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-expr"
|
name = "cfg-expr"
|
||||||
version = "0.20.5"
|
version = "0.20.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720"
|
checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
@@ -429,9 +429,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
@@ -782,7 +782,7 @@ dependencies = [
|
|||||||
"epaint",
|
"epaint",
|
||||||
"log",
|
"log",
|
||||||
"profiling",
|
"profiling",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"type-map",
|
"type-map",
|
||||||
"web-time",
|
"web-time",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
@@ -944,15 +944,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.6"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.5"
|
version = "1.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -1000,6 +1000,31 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gethostname"
|
name = "gethostname"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1012,9 +1037,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1342,9 +1367,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -1364,9 +1389,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.179"
|
version = "0.2.180"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
@@ -1380,9 +1405,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.15"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
@@ -1553,7 +1578,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2092,6 +2117,12 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pipewire"
|
name = "pipewire"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
@@ -2106,7 +2137,7 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pipewire-sys",
|
"pipewire-sys",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2194,9 +2225,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -2209,7 +2240,7 @@ checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwsp"
|
name = "pwsp"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2251,9 +2282,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.43"
|
version = "1.0.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -2323,9 +2354,9 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.17",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2365,9 +2396,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfd"
|
name = "rfd"
|
||||||
version = "0.17.1"
|
version = "0.17.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "069d6129dede311430d0dcf1ded88a7affc7a342c2d8e6336043d43ed14dac17"
|
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.6.2",
|
"block2 0.6.2",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
@@ -2598,7 +2629,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"rustix 1.1.3",
|
"rustix 1.1.3",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-csd-frame",
|
"wayland-csd-frame",
|
||||||
@@ -2633,9 +2664,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
@@ -2906,11 +2937,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.17"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.17",
|
"thiserror-impl 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2926,9 +2957,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.17"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2989,9 +3020,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.10+spec-1.1.0"
|
version = "0.9.11+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -3136,18 +3167,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.1+wasi-0.2.4"
|
version = "1.0.2+wasi-0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.106"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -3158,11 +3189,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.56"
|
version = "0.4.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
|
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3171,9 +3203,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.106"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -3181,9 +3213,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.106"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -3194,9 +3226,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.106"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -3338,9 +3370,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
|
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3425,7 +3457,7 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"wgpu-core-deps-windows-linux-android",
|
"wgpu-core-deps-windows-linux-android",
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
@@ -3456,7 +3488,7 @@ dependencies = [
|
|||||||
"portable-atomic-util",
|
"portable-atomic-util",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"renderdoc-sys",
|
"renderdoc-sys",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3470,7 +3502,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.18",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3811,9 +3843,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.46.0"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
@@ -3983,9 +4015,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.12"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
|
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pwsp"
|
name = "pwsp"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["arabian"]
|
authors = ["arabian"]
|
||||||
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
|
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
|
||||||
@@ -23,7 +23,7 @@ dirs = "6.0.0"
|
|||||||
|
|
||||||
rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] }
|
rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] }
|
||||||
pipewire = "0.9.2"
|
pipewire = "0.9.2"
|
||||||
rfd = { version = "0.17.1", default-features = false, features = ["xdg-portal"]}
|
rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"]}
|
||||||
|
|
||||||
egui = { version = "0.33.3", default-features = false, features = ["default_fonts", "rayon"] }
|
egui = { version = "0.33.3", default-features = false, features = ["default_fonts", "rayon"] }
|
||||||
eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] }
|
eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] }
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ three main components:
|
|||||||
* Creating and managing virtual audio devices.
|
* Creating and managing virtual audio devices.
|
||||||
* Linking these devices within the PipeWire graph.
|
* Linking these devices within the PipeWire graph.
|
||||||
* Handling all audio playback.
|
* Handling all audio playback.
|
||||||
* **pwsp-gui**: This is the graphical user interface. It acts as a client that communicates with pwsp-daemon via a *
|
* **pwsp-gui**: This is the graphical user interface. It acts as a client that communicates with pwsp-daemon via a
|
||||||
*UnixSocket**. This is how you interact with your sound collection, control playback, and configure settings.
|
**UnixSocket**. This is how you interact with your sound collection, control playback, and configure settings.
|
||||||
* **pwsp-cli**: This is the command-line interface, also acting as a client. It provides a way to control the daemon
|
* **pwsp-cli**: This is the command-line interface, also acting as a client. It provides a way to control the daemon
|
||||||
without a GUI, allowing for scripting or quick command-based actions.
|
without a GUI, allowing for scripting or quick command-based actions.
|
||||||
|
|
||||||
@@ -44,10 +44,10 @@ three main components:
|
|||||||
|
|
||||||
## **Pre-built Packages**
|
## **Pre-built Packages**
|
||||||
|
|
||||||
You can download pre-built binaries, .deb and .rpm packages from
|
You can download pre-built binaries and .deb packages from
|
||||||
the [releases page](https://github.com/arabianq/pipewire-soundpad/releases).
|
the [releases page](https://github.com/arabianq/pipewire-soundpad/releases).
|
||||||
|
|
||||||
## **Fedora Linux**
|
## **Fedora Linux (and derivatives)**
|
||||||
|
|
||||||
If you're using Fedora, you can install PWSP from a dedicated repository using DNF.
|
If you're using Fedora, you can install PWSP from a dedicated repository using DNF.
|
||||||
|
|
||||||
@@ -137,12 +137,12 @@ You can start the daemon from the terminal or enable the systemd service for aut
|
|||||||
|
|
||||||
### **Using the GUI**
|
### **Using the GUI**
|
||||||
|
|
||||||
1. **Add Sounds**: Click the **"Add Directory"** button and select a folder containing your audio files. The application
|
1. **Add Sounds**: Click the **"+"** button and select a folder containing your audio files. The application
|
||||||
will automatically list all supported files.
|
will automatically list all supported files.
|
||||||
2. **Select Microphone**: In the main application window, select your **physical microphone**. PWSP will automatically
|
2. **Select Microphone**: In the main application window, select your microphone. PWSP will automatically
|
||||||
create a virtual microphone and feed it sound from two sources: **your microphone** and the **audio files**.
|
create a virtual microphone and feed it sound from two sources: **your microphone** and the **audio files**.
|
||||||
3. **Playback**: Click on a file in the list to load it, then use the **"Play"** and **"Pause"** buttons to control
|
3. **Playback**: Click on a file in the list to load it, then use the **"Play"** and **"Pause"** buttons to control
|
||||||
playback.
|
playback. You can also play single file once using **"Play File"** button.
|
||||||
|
|
||||||
### **Using the CLI**
|
### **Using the CLI**
|
||||||
|
|
||||||
@@ -173,6 +173,29 @@ pwsp-cli --help
|
|||||||
pwsp-cli set position 20
|
pwsp-cli set position 20
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### **Hotkeys & Controls**
|
||||||
|
|
||||||
|
#### **Keyboard Shortcuts**
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
| :----------------------- | :--------------------------------------------------- |
|
||||||
|
| **Esc** | Close application |
|
||||||
|
| **Space** | Pause / Resume audio |
|
||||||
|
| **Backspace** | Stop all audio tracks |
|
||||||
|
| **Enter** | Play selected file (stops all other tracks) |
|
||||||
|
| **Ctrl + Enter** | Add selected file to playback (plays simultaneously) |
|
||||||
|
| **Shift + Enter** | Replace the last added track with the selected one |
|
||||||
|
| **I** | Open / Close settings |
|
||||||
|
| **/** | Focus search field |
|
||||||
|
| **Ctrl + ↑ / ↓** | Navigate through files |
|
||||||
|
| **Ctrl + Shift + ↑ / ↓** | Navigate through directories |
|
||||||
|
|
||||||
|
#### **Mouse Controls**
|
||||||
|
|
||||||
|
* **Left Click**: Play track (stops all other tracks).
|
||||||
|
* **Ctrl + Left Click**: Add track (plays simultaneously with current tracks).
|
||||||
|
* **Shift + Left Click**: Replace the last added track with the selected one.
|
||||||
|
|
||||||
# **🤝 Contributing**
|
# **🤝 Contributing**
|
||||||
|
|
||||||
Contributions are welcome\! If you have ideas for improvements or find a bug, feel free to create
|
Contributions are welcome\! If you have ideas for improvements or find a bug, feel free to create
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 70 KiB |
@@ -4,7 +4,7 @@
|
|||||||
%global cargo_install_lib 0
|
%global cargo_install_lib 0
|
||||||
|
|
||||||
Name: pwsp
|
Name: pwsp
|
||||||
Version: 1.3.1
|
Version: 1.4.0
|
||||||
Release: %autorelease
|
Release: %autorelease
|
||||||
Summary: Lets you play audio files through your microphone
|
Summary: Lets you play audio files through your microphone
|
||||||
|
|
||||||
|
|||||||
+65
-28
@@ -36,17 +36,36 @@ enum Actions {
|
|||||||
/// Ping the daemon
|
/// Ping the daemon
|
||||||
Ping,
|
Ping,
|
||||||
/// Pause audio playback
|
/// Pause audio playback
|
||||||
Pause,
|
Pause {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Resume audio playback
|
/// Resume audio playback
|
||||||
Resume,
|
Resume {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Toggle pause
|
/// Toggle pause
|
||||||
TogglePause,
|
TogglePause {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Stop audio playback and clear the queue
|
/// Stop audio playback and clear the queue
|
||||||
Stop,
|
Stop {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Play a file
|
/// Play a file
|
||||||
Play { file_path: PathBuf },
|
Play {
|
||||||
|
file_path: PathBuf,
|
||||||
|
#[clap(short, long)]
|
||||||
|
concurrent: bool,
|
||||||
|
},
|
||||||
/// Toggle loop
|
/// Toggle loop
|
||||||
ToggleLoop,
|
ToggleLoop {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
@@ -56,31 +75,47 @@ enum GetCommands {
|
|||||||
/// Playback volume
|
/// Playback volume
|
||||||
Volume,
|
Volume,
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position,
|
Position {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Duration of the current file
|
/// Duration of the current file
|
||||||
Duration,
|
Duration {
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Player state (Playing, Paused or Stopped)
|
/// Player state (Playing, Paused or Stopped)
|
||||||
State,
|
State,
|
||||||
/// Current playing file path
|
/// Get all playing tracks
|
||||||
CurrentFilePath,
|
Tracks,
|
||||||
/// Current audio input
|
/// Current audio input
|
||||||
Input,
|
Input,
|
||||||
/// All audio inputs
|
/// All audio inputs
|
||||||
Inputs,
|
Inputs,
|
||||||
/// Is loop enabled (true or false)
|
|
||||||
Loop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum SetCommands {
|
enum SetCommands {
|
||||||
/// Playback volume
|
/// Playback volume
|
||||||
Volume { volume: f32 },
|
Volume {
|
||||||
|
volume: f32,
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position { position: f32 },
|
Position {
|
||||||
|
position: f32,
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
/// Audio input id (see pwsp-cli get inputs)
|
/// Audio input id (see pwsp-cli get inputs)
|
||||||
Input { name: String },
|
Input { name: String },
|
||||||
/// Enable or disable loop (true or false)
|
/// Enable or disable loop (true or false)
|
||||||
Loop { enabled: String },
|
Loop {
|
||||||
|
enabled: String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
id: Option<u32>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -92,29 +127,31 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let request = match cli.command {
|
let request = match cli.command {
|
||||||
Commands::Action { action } => match action {
|
Commands::Action { action } => match action {
|
||||||
Actions::Ping => Request::ping(),
|
Actions::Ping => Request::ping(),
|
||||||
Actions::Pause => Request::pause(),
|
Actions::Pause { id } => Request::pause(id),
|
||||||
Actions::Resume => Request::resume(),
|
Actions::Resume { id } => Request::resume(id),
|
||||||
Actions::TogglePause => Request::toggle_pause(),
|
Actions::TogglePause { id } => Request::toggle_pause(id),
|
||||||
Actions::Stop => Request::stop(),
|
Actions::Stop { id } => Request::stop(id),
|
||||||
Actions::Play { file_path } => Request::play(file_path.to_str().unwrap()),
|
Actions::Play {
|
||||||
Actions::ToggleLoop => Request::toggle_loop(),
|
file_path,
|
||||||
|
concurrent,
|
||||||
|
} => Request::play(file_path.to_str().unwrap(), concurrent),
|
||||||
|
Actions::ToggleLoop { id } => Request::toggle_loop(id),
|
||||||
},
|
},
|
||||||
Commands::Get { parameter } => match parameter {
|
Commands::Get { parameter } => match parameter {
|
||||||
GetCommands::IsPaused => Request::get_is_paused(),
|
GetCommands::IsPaused => Request::get_is_paused(),
|
||||||
GetCommands::Volume => Request::get_volume(),
|
GetCommands::Volume => Request::get_volume(),
|
||||||
GetCommands::Position => Request::get_position(),
|
GetCommands::Position { id } => Request::get_position(id),
|
||||||
GetCommands::Duration => Request::get_duration(),
|
GetCommands::Duration { id } => Request::get_duration(id),
|
||||||
GetCommands::State => Request::get_state(),
|
GetCommands::State => Request::get_state(),
|
||||||
GetCommands::CurrentFilePath => Request::get_current_file_path(),
|
GetCommands::Tracks => Request::get_tracks(),
|
||||||
GetCommands::Input => Request::get_input(),
|
GetCommands::Input => Request::get_input(),
|
||||||
GetCommands::Inputs => Request::get_inputs(),
|
GetCommands::Inputs => Request::get_inputs(),
|
||||||
GetCommands::Loop => Request::get_loop(),
|
|
||||||
},
|
},
|
||||||
Commands::Set { parameter } => match parameter {
|
Commands::Set { parameter } => match parameter {
|
||||||
SetCommands::Volume { volume } => Request::set_volume(volume),
|
SetCommands::Volume { volume, id } => Request::set_volume(volume, id),
|
||||||
SetCommands::Position { position } => Request::seek(position),
|
SetCommands::Position { position, id } => Request::seek(position, id),
|
||||||
SetCommands::Input { name } => Request::set_input(&name),
|
SetCommands::Input { name } => Request::set_input(&name),
|
||||||
SetCommands::Loop { enabled } => Request::set_loop(&enabled),
|
SetCommands::Loop { enabled, id } => Request::set_loop(&enabled, id),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-16
@@ -1,8 +1,5 @@
|
|||||||
use pwsp::{
|
use pwsp::{
|
||||||
types::{
|
types::socket::{Request, Response},
|
||||||
audio_player::PlayerState,
|
|
||||||
socket::{Request, Response},
|
|
||||||
},
|
|
||||||
utils::{
|
utils::{
|
||||||
commands::parse_command,
|
commands::parse_command,
|
||||||
daemon::{
|
daemon::{
|
||||||
@@ -122,18 +119,7 @@ async fn player_loop() {
|
|||||||
loop {
|
loop {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
|
|
||||||
// Start playback again if loop is enabled
|
audio_player.update().await;
|
||||||
let should_play = audio_player.get_state() == PlayerState::Stopped
|
|
||||||
&& audio_player.current_file_path.is_some()
|
|
||||||
&& audio_player.looped;
|
|
||||||
|
|
||||||
if should_play {
|
|
||||||
let file_path = audio_player.current_file_path.clone().unwrap();
|
|
||||||
audio_player
|
|
||||||
.play(&file_path)
|
|
||||||
.await
|
|
||||||
.expect("Something went wrong while trying to play the file");
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|||||||
+164
-48
@@ -4,11 +4,32 @@ use egui::{
|
|||||||
Slider, TextEdit, Ui, Vec2,
|
Slider, TextEdit, Ui, Vec2,
|
||||||
};
|
};
|
||||||
use egui_material_icons::icons;
|
use egui_material_icons::icons;
|
||||||
use pwsp::types::audio_player::PlayerState;
|
use pwsp::types::audio_player::TrackInfo;
|
||||||
use pwsp::utils::gui::format_time_pair;
|
use pwsp::utils::gui::format_time_pair;
|
||||||
use std::{error::Error, path::PathBuf};
|
use std::{error::Error, path::PathBuf, time::Instant};
|
||||||
|
|
||||||
|
use pwsp::types::gui::AppState;
|
||||||
|
|
||||||
|
enum TrackAction {
|
||||||
|
Pause(u32),
|
||||||
|
Resume(u32),
|
||||||
|
ToggleLoop(u32),
|
||||||
|
Stop(u32),
|
||||||
|
}
|
||||||
|
|
||||||
impl SoundpadGui {
|
impl SoundpadGui {
|
||||||
|
fn get_volume_icon(volume: f32) -> &'static str {
|
||||||
|
if volume > 0.7 {
|
||||||
|
icons::ICON_VOLUME_UP
|
||||||
|
} else if volume <= 0.0 {
|
||||||
|
icons::ICON_VOLUME_OFF
|
||||||
|
} else if volume < 0.3 {
|
||||||
|
icons::ICON_VOLUME_MUTE
|
||||||
|
} else {
|
||||||
|
icons::ICON_VOLUME_DOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_waiting_for_daemon(&mut self, ui: &mut Ui) {
|
pub fn draw_waiting_for_daemon(&mut self, ui: &mut Ui) {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
@@ -74,11 +95,24 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
fn draw_header(&mut self, ui: &mut Ui) {
|
fn draw_header(&mut self, ui: &mut Ui) {
|
||||||
ui.vertical_centered_justified(|ui| {
|
ui.vertical_centered_justified(|ui| {
|
||||||
// Current file name
|
self.draw_controls(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_controls(&mut self, ui: &mut Ui) {
|
||||||
|
if self.audio_player_state.tracks.is_empty() {
|
||||||
|
ui.label("No tracks playing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tracks = self.audio_player_state.tracks.clone();
|
||||||
|
let mut action = None;
|
||||||
|
|
||||||
|
for track in tracks {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(
|
RichText::new(
|
||||||
self.audio_player_state
|
track
|
||||||
.current_file_path
|
.path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_str()
|
.to_str()
|
||||||
@@ -87,32 +121,76 @@ impl SoundpadGui {
|
|||||||
.color(Color32::WHITE)
|
.color(Color32::WHITE)
|
||||||
.family(FontFamily::Monospace),
|
.family(FontFamily::Monospace),
|
||||||
);
|
);
|
||||||
// Media controls
|
if let Some(act) = Self::draw_track_control(ui, &mut self.app_state, &track) {
|
||||||
self.draw_controls(ui);
|
action = Some(act);
|
||||||
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if let Some(action) = action {
|
||||||
|
match action {
|
||||||
|
TrackAction::Pause(id) => self.pause(Some(id)),
|
||||||
|
TrackAction::Resume(id) => self.resume(Some(id)),
|
||||||
|
TrackAction::ToggleLoop(id) => self.toggle_loop(Some(id)),
|
||||||
|
TrackAction::Stop(id) => self.stop(Some(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_controls(&mut self, ui: &mut Ui) {
|
fn draw_track_control(
|
||||||
|
ui: &mut Ui,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
track: &TrackInfo,
|
||||||
|
) -> Option<TrackAction> {
|
||||||
|
let ui_state = app_state.track_ui_states.entry(track.id).or_default();
|
||||||
|
|
||||||
|
let should_update_position = !ui_state.position_dragged
|
||||||
|
&& ui_state
|
||||||
|
.ignore_position_update_until
|
||||||
|
.map(|t| Instant::now() > t)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if should_update_position {
|
||||||
|
ui_state.position_slider_value = track.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
let should_update_volume = !ui_state.volume_dragged
|
||||||
|
&& ui_state
|
||||||
|
.ignore_volume_update_until
|
||||||
|
.map(|t| Instant::now() > t)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if should_update_volume {
|
||||||
|
ui_state.volume_slider_value = track.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut action = None;
|
||||||
|
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
// ---------- Play Button ----------
|
// ---------- Play Button ----------
|
||||||
let play_button = Button::new(match self.audio_player_state.state {
|
let play_button = Button::new(if track.paused {
|
||||||
PlayerState::Playing => icons::ICON_PAUSE,
|
icons::ICON_PLAY_ARROW
|
||||||
PlayerState::Paused | PlayerState::Stopped => icons::ICON_PLAY_ARROW,
|
} else {
|
||||||
|
icons::ICON_PAUSE
|
||||||
})
|
})
|
||||||
.corner_radius(15.0);
|
.corner_radius(15.0);
|
||||||
|
|
||||||
let play_button_response = ui.add_sized([30.0, 30.0], play_button);
|
let play_button_response = ui.add_sized([30.0, 30.0], play_button);
|
||||||
if play_button_response.clicked() {
|
if play_button_response.clicked() {
|
||||||
self.play_toggle();
|
if track.paused {
|
||||||
|
action = Some(TrackAction::Resume(track.id));
|
||||||
|
} else {
|
||||||
|
action = Some(TrackAction::Pause(track.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Loop Button ----------
|
// ---------- Loop Button ----------
|
||||||
let loop_button = Button::new(
|
let loop_button = Button::new(
|
||||||
RichText::new(match self.audio_player_state.looped {
|
RichText::new(if track.looped {
|
||||||
true => icons::ICON_REPEAT_ONE,
|
icons::ICON_REPEAT_ONE
|
||||||
false => icons::ICON_REPEAT,
|
} else {
|
||||||
|
icons::ICON_REPEAT
|
||||||
})
|
})
|
||||||
.size(18.0),
|
.size(18.0),
|
||||||
)
|
)
|
||||||
@@ -120,17 +198,15 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
let loop_button_response = ui.add_sized([15.0, 30.0], loop_button);
|
let loop_button_response = ui.add_sized([15.0, 30.0], loop_button);
|
||||||
if loop_button_response.clicked() {
|
if loop_button_response.clicked() {
|
||||||
self.toggle_loop();
|
action = Some(TrackAction::ToggleLoop(track.id));
|
||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Position Slider ----------
|
// ---------- Position Slider ----------
|
||||||
let position_slider = Slider::new(
|
let duration = track.duration.unwrap_or(1.0);
|
||||||
&mut self.app_state.position_slider_value,
|
let position_slider = Slider::new(&mut ui_state.position_slider_value, 0.0..=duration)
|
||||||
0.0..=self.audio_player_state.duration,
|
.show_value(false)
|
||||||
)
|
.step_by(0.01);
|
||||||
.show_value(false)
|
|
||||||
.step_by(0.01);
|
|
||||||
|
|
||||||
let default_slider_width = ui.spacing().slider_width;
|
let default_slider_width = ui.spacing().slider_width;
|
||||||
let position_slider_width = ui.available_width()
|
let position_slider_width = ui.available_width()
|
||||||
@@ -140,49 +216,47 @@ impl SoundpadGui {
|
|||||||
ui.spacing_mut().slider_width = position_slider_width;
|
ui.spacing_mut().slider_width = position_slider_width;
|
||||||
let position_slider_response = ui.add_sized([30.0, 30.0], position_slider);
|
let position_slider_response = ui.add_sized([30.0, 30.0], position_slider);
|
||||||
if position_slider_response.drag_stopped() {
|
if position_slider_response.drag_stopped() {
|
||||||
self.app_state.position_dragged = true;
|
ui_state.position_dragged = true;
|
||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Time Label ----------
|
// ---------- Time Label ----------
|
||||||
let time_label = Label::new(
|
let time_label =
|
||||||
RichText::new(format_time_pair(
|
Label::new(RichText::new(format_time_pair(track.position, duration)).monospace());
|
||||||
self.audio_player_state.position,
|
|
||||||
self.audio_player_state.duration,
|
|
||||||
))
|
|
||||||
.monospace(),
|
|
||||||
);
|
|
||||||
ui.add_sized([30.0, 30.0], time_label);
|
ui.add_sized([30.0, 30.0], time_label);
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Volume Icon ----------
|
// ---------- Volume Icon ----------
|
||||||
let volume_icon = if self.audio_player_state.volume > 0.7 {
|
let volume_icon = Self::get_volume_icon(track.volume);
|
||||||
icons::ICON_VOLUME_UP
|
let volume_label = Label::new(RichText::new(volume_icon).size(18.0));
|
||||||
} else if self.audio_player_state.volume == 0.0 {
|
ui.add_sized([30.0, 30.0], volume_label)
|
||||||
icons::ICON_VOLUME_OFF
|
.on_hover_text(format!("Volume: {:.0}%", track.volume * 100.0));
|
||||||
} else if self.audio_player_state.volume < 0.3 {
|
|
||||||
icons::ICON_VOLUME_MUTE
|
|
||||||
} else {
|
|
||||||
icons::ICON_VOLUME_DOWN
|
|
||||||
};
|
|
||||||
let volume_icon = Label::new(RichText::new(volume_icon).size(18.0));
|
|
||||||
ui.add_sized([30.0, 30.0], volume_icon);
|
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Volume Slider ----------
|
// ---------- Volume Slider ----------
|
||||||
let volume_slider = Slider::new(&mut self.app_state.volume_slider_value, 0.0..=1.0)
|
let volume_slider = Slider::new(&mut ui_state.volume_slider_value, 0.0..=1.0)
|
||||||
.show_value(false)
|
.show_value(false)
|
||||||
.step_by(0.01);
|
.step_by(0.01);
|
||||||
|
|
||||||
ui.spacing_mut().slider_width = default_slider_width;
|
ui.spacing_mut().slider_width = default_slider_width - 30.0;
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
|
||||||
let volume_slider_response = ui.add_sized([30.0, 30.0], volume_slider);
|
let volume_slider_response = ui.add_sized([30.0, 30.0], volume_slider);
|
||||||
if volume_slider_response.drag_stopped() {
|
if volume_slider_response.drag_stopped() {
|
||||||
self.app_state.volume_dragged = true;
|
ui_state.volume_dragged = true;
|
||||||
|
}
|
||||||
|
// --------------------------------
|
||||||
|
|
||||||
|
// ---------- Stop Button ---------
|
||||||
|
let stop_button = Button::new(icons::ICON_CLOSE).frame(false);
|
||||||
|
let stop_button_response = ui.add_sized([30.0, 30.0], stop_button);
|
||||||
|
if stop_button_response.clicked() {
|
||||||
|
action = Some(TrackAction::Stop(track.id));
|
||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
});
|
});
|
||||||
|
|
||||||
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_body(&mut self, ui: &mut Ui) {
|
fn draw_body(&mut self, ui: &mut Ui) {
|
||||||
@@ -316,7 +390,18 @@ impl SoundpadGui {
|
|||||||
let file_button = Button::new(file_button_text).frame(false);
|
let file_button = Button::new(file_button_text).frame(false);
|
||||||
let file_button_response = ui.add(file_button);
|
let file_button_response = ui.add(file_button);
|
||||||
if file_button_response.clicked() {
|
if file_button_response.clicked() {
|
||||||
self.play_file(&entry_path);
|
ui.input(|i| {
|
||||||
|
if i.modifiers.ctrl {
|
||||||
|
self.play_file(&entry_path, true);
|
||||||
|
} else if i.modifiers.shift
|
||||||
|
&& let Some(last_track) = self.audio_player_state.tracks.last()
|
||||||
|
{
|
||||||
|
self.stop(Some(last_track.id));
|
||||||
|
self.play_file(&entry_path, true);
|
||||||
|
} else {
|
||||||
|
self.play_file(&entry_path, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
self.app_state.selected_file = Some(entry_path);
|
self.app_state.selected_file = Some(entry_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +412,7 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
fn draw_footer(&mut self, ui: &mut Ui) {
|
fn draw_footer(&mut self, ui: &mut Ui) {
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// ---------- Microphone selection ----------
|
// ---------- Microphone selection ----------
|
||||||
let mut mics: Vec<(&String, &String)> =
|
let mut mics: Vec<(&String, &String)> =
|
||||||
self.audio_player_state.all_inputs.iter().collect();
|
self.audio_player_state.all_inputs.iter().collect();
|
||||||
@@ -336,6 +421,7 @@ impl SoundpadGui {
|
|||||||
let mut selected_input = self.audio_player_state.current_input.to_owned();
|
let mut selected_input = self.audio_player_state.current_input.to_owned();
|
||||||
let prev_input = selected_input.to_owned();
|
let prev_input = selected_input.to_owned();
|
||||||
ComboBox::from_label("Choose microphone")
|
ComboBox::from_label("Choose microphone")
|
||||||
|
.height(30.0)
|
||||||
.selected_text(
|
.selected_text(
|
||||||
self.audio_player_state
|
self.audio_player_state
|
||||||
.all_inputs
|
.all_inputs
|
||||||
@@ -353,10 +439,40 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
|
// ---------- Master Volume Slider ----------
|
||||||
|
let volume_icon = Self::get_volume_icon(self.audio_player_state.volume);
|
||||||
|
let volume_label = Label::new(RichText::new(volume_icon).size(18.0));
|
||||||
|
ui.add_sized([18.0, 18.0], volume_label)
|
||||||
|
.on_hover_text(format!(
|
||||||
|
"Master Volume: {:.0}%",
|
||||||
|
self.audio_player_state.volume * 100.0
|
||||||
|
));
|
||||||
|
|
||||||
|
let should_update_volume = !self.app_state.volume_dragged
|
||||||
|
&& self
|
||||||
|
.app_state
|
||||||
|
.ignore_volume_update_until
|
||||||
|
.map(|t| Instant::now() > t)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if should_update_volume {
|
||||||
|
self.app_state.volume_slider_value = self.audio_player_state.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
let volume_slider = Slider::new(&mut self.app_state.volume_slider_value, 0.0..=1.0)
|
||||||
|
.show_value(false)
|
||||||
|
.step_by(0.01);
|
||||||
|
let volume_slider_response = ui.add_sized([150.0, 18.0], volume_slider);
|
||||||
|
if volume_slider_response.drag_stopped() {
|
||||||
|
self.app_state.volume_dragged = true;
|
||||||
|
}
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
ui.add_space(ui.available_width() - 18.0 - ui.spacing().item_spacing.x);
|
ui.add_space(ui.available_width() - 18.0 - ui.spacing().item_spacing.x);
|
||||||
|
|
||||||
// ---------- Settings button ----------
|
// ---------- Settings button ----------
|
||||||
let settings_button = Button::new(icons::ICON_SETTINGS).frame(false);
|
let settings_button =
|
||||||
|
Button::new(icons::ICON_SETTINGS.atom_size(Vec2::new(18.0, 18.0))).frame(false);
|
||||||
let settings_button_response = ui.add_sized([18.0, 18.0], settings_button);
|
let settings_button_response = ui.add_sized([18.0, 18.0], settings_button);
|
||||||
if settings_button_response.clicked() {
|
if settings_button_response.clicked() {
|
||||||
self.app_state.show_settings = true;
|
self.app_state.show_settings = true;
|
||||||
|
|||||||
+16
-1
@@ -21,7 +21,17 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i.key_pressed(Key::Enter) && self.app_state.selected_file.is_some() {
|
if i.key_pressed(Key::Enter) && self.app_state.selected_file.is_some() {
|
||||||
self.play_file(&self.app_state.selected_file.clone().unwrap());
|
let path = &self.app_state.selected_file.clone().unwrap();
|
||||||
|
if i.modifiers.ctrl {
|
||||||
|
self.play_file(path, true);
|
||||||
|
} else if i.modifiers.shift
|
||||||
|
&& let Some(last_track) = self.audio_player_state.tracks.last()
|
||||||
|
{
|
||||||
|
self.stop(Some(last_track.id));
|
||||||
|
self.play_file(path, true);
|
||||||
|
} else {
|
||||||
|
self.play_file(path, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.app_state.show_settings {
|
if !self.app_state.show_settings {
|
||||||
@@ -30,6 +40,11 @@ impl SoundpadGui {
|
|||||||
self.play_toggle();
|
self.play_toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop all audio tracks on backspace
|
||||||
|
if i.key_pressed(Key::Backspace) {
|
||||||
|
self.stop(None);
|
||||||
|
}
|
||||||
|
|
||||||
// Focus search field
|
// Focus search field
|
||||||
if i.key_pressed(Key::Slash) && self.app_state.search_field_id.is_some() {
|
if i.key_pressed(Key::Slash) && self.app_state.search_field_id.is_some() {
|
||||||
self.app_state.force_focus_id = self.app_state.search_field_id;
|
self.app_state.force_focus_id = self.app_state.search_field_id;
|
||||||
|
|||||||
+24
-12
@@ -13,13 +13,13 @@ use pwsp::{
|
|||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
daemon::get_daemon_config,
|
daemon::get_daemon_config,
|
||||||
gui::{get_gui_config, make_request_sync, start_app_state_thread},
|
gui::{get_gui_config, make_request_async, make_request_sync, start_app_state_thread},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,14 +59,14 @@ impl SoundpadGui {
|
|||||||
let (new_state, request) = {
|
let (new_state, request) = {
|
||||||
let guard = self.audio_player_state_shared.lock().unwrap();
|
let guard = self.audio_player_state_shared.lock().unwrap();
|
||||||
match guard.state {
|
match guard.state {
|
||||||
PlayerState::Playing => (Some(PlayerState::Paused), Some(Request::pause())),
|
PlayerState::Playing => (Some(PlayerState::Paused), Some(Request::pause(None))),
|
||||||
PlayerState::Paused => (Some(PlayerState::Playing), Some(Request::resume())),
|
PlayerState::Paused => (Some(PlayerState::Playing), Some(Request::resume(None))),
|
||||||
PlayerState::Stopped => (None, None),
|
PlayerState::Stopped => (None, None),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(req) = request {
|
if let Some(req) = request {
|
||||||
make_request_sync(req).ok();
|
make_request_async(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(state) = new_state {
|
if let Some(state) = new_state {
|
||||||
@@ -79,7 +79,7 @@ impl SoundpadGui {
|
|||||||
pub fn open_file(&mut self) {
|
pub fn open_file(&mut self) {
|
||||||
let file_dialog = FileDialog::new().add_filter("Audio File", &SUPPORTED_EXTENSIONS);
|
let file_dialog = FileDialog::new().add_filter("Audio File", &SUPPORTED_EXTENSIONS);
|
||||||
if let Some(path) = file_dialog.pick_file() {
|
if let Some(path) = file_dialog.pick_file() {
|
||||||
self.play_file(&path);
|
self.play_file(&path, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +116,12 @@ impl SoundpadGui {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_file(&mut self, path: &PathBuf) {
|
pub fn play_file(&mut self, path: &PathBuf, concurrent: bool) {
|
||||||
make_request_sync(Request::play(path.to_str().unwrap())).ok();
|
make_request_async(Request::play(path.to_str().unwrap(), concurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_input(&mut self, name: String) {
|
pub fn set_input(&mut self, name: String) {
|
||||||
make_request_sync(Request::set_input(&name)).ok();
|
make_request_async(Request::set_input(&name));
|
||||||
|
|
||||||
if self.config.save_input {
|
if self.config.save_input {
|
||||||
let mut daemon_config = get_daemon_config();
|
let mut daemon_config = get_daemon_config();
|
||||||
@@ -130,8 +130,20 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_loop(&mut self) {
|
pub fn toggle_loop(&mut self, id: Option<u32>) {
|
||||||
make_request_sync(Request::toggle_loop()).ok();
|
make_request_async(Request::toggle_loop(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self, id: Option<u32>) {
|
||||||
|
make_request_async(Request::pause(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume(&mut self, id: Option<u32>) {
|
||||||
|
make_request_async(Request::resume(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self, id: Option<u32>) {
|
||||||
|
make_request_async(Request::stop(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +175,7 @@ pub async fn run() -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let config = get_gui_config();
|
let config = get_gui_config();
|
||||||
if config.pause_on_exit {
|
if config.pause_on_exit {
|
||||||
make_request_sync(Request::pause()).ok();
|
make_request_sync(Request::pause(None)).ok();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-35
@@ -3,14 +3,60 @@ use eframe::{App, Frame as EFrame};
|
|||||||
use egui::{CentralPanel, Context};
|
use egui::{CentralPanel, Context};
|
||||||
use pwsp::{
|
use pwsp::{
|
||||||
types::socket::Request,
|
types::socket::Request,
|
||||||
utils::{
|
utils::{daemon::get_daemon_config, gui::make_request_async},
|
||||||
daemon::{get_daemon_config, is_daemon_running},
|
|
||||||
gui::make_request_sync,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
impl App for SoundpadGui {
|
impl App for SoundpadGui {
|
||||||
fn update(&mut self, ctx: &Context, _frame: &mut EFrame) {
|
fn update(&mut self, ctx: &Context, _frame: &mut EFrame) {
|
||||||
|
let mut seek_requests = vec![];
|
||||||
|
let mut volume_requests = vec![];
|
||||||
|
|
||||||
|
for (id, ui_state) in &mut self.app_state.track_ui_states {
|
||||||
|
if ui_state.position_dragged {
|
||||||
|
seek_requests.push((*id, ui_state.position_slider_value));
|
||||||
|
}
|
||||||
|
if ui_state.volume_dragged {
|
||||||
|
volume_requests.push((*id, ui_state.volume_slider_value));
|
||||||
|
ui_state.volume_dragged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, pos) in seek_requests {
|
||||||
|
make_request_async(Request::seek(pos, Some(id)));
|
||||||
|
if let Some(ui_state) = self.app_state.track_ui_states.get_mut(&id) {
|
||||||
|
ui_state.position_dragged = false;
|
||||||
|
ui_state.ignore_position_update_until =
|
||||||
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, vol) in volume_requests {
|
||||||
|
make_request_async(Request::set_volume(vol, Some(id)));
|
||||||
|
if let Some(ui_state) = self.app_state.track_ui_states.get_mut(&id) {
|
||||||
|
ui_state.volume_dragged = false;
|
||||||
|
ui_state.ignore_volume_update_until =
|
||||||
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.app_state.volume_dragged {
|
||||||
|
make_request_async(Request::set_volume(
|
||||||
|
self.app_state.volume_slider_value,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.app_state.volume_dragged = false;
|
||||||
|
self.app_state.ignore_volume_update_until =
|
||||||
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
|
||||||
|
if self.config.save_volume {
|
||||||
|
let mut daemon_config = get_daemon_config();
|
||||||
|
daemon_config.default_volume = Some(self.app_state.volume_slider_value);
|
||||||
|
daemon_config.save_to_file().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let guard = self.audio_player_state_shared.lock().unwrap();
|
let guard = self.audio_player_state_shared.lock().unwrap();
|
||||||
self.audio_player_state = guard.clone();
|
self.audio_player_state = guard.clone();
|
||||||
@@ -29,7 +75,7 @@ impl App for SoundpadGui {
|
|||||||
self.handle_input(ctx);
|
self.handle_input(ctx);
|
||||||
|
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
if !is_daemon_running().unwrap() {
|
if !self.audio_player_state.is_daemon_running {
|
||||||
self.draw_waiting_for_daemon(ui);
|
self.draw_waiting_for_daemon(ui);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -49,36 +95,6 @@ impl App for SoundpadGui {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.app_state.position_dragged {
|
|
||||||
make_request_sync(Request::seek(self.app_state.position_slider_value)).ok();
|
|
||||||
let mut guard = self.audio_player_state_shared.lock().unwrap();
|
|
||||||
guard.new_position = Some(self.app_state.position_slider_value);
|
|
||||||
guard.position = self.app_state.position_slider_value;
|
|
||||||
self.app_state.position_dragged = false;
|
|
||||||
} else {
|
|
||||||
self.app_state.position_slider_value = self.audio_player_state.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app_state.volume_dragged {
|
|
||||||
let new_volume = self.app_state.volume_slider_value;
|
|
||||||
|
|
||||||
make_request_sync(Request::set_volume(new_volume)).ok();
|
|
||||||
|
|
||||||
let mut guard = self.audio_player_state_shared.lock().unwrap();
|
|
||||||
guard.new_volume = Some(self.app_state.volume_slider_value);
|
|
||||||
guard.volume = self.app_state.volume_slider_value;
|
|
||||||
|
|
||||||
self.app_state.volume_dragged = false;
|
|
||||||
|
|
||||||
if self.config.save_volume {
|
|
||||||
let mut daemon_config = get_daemon_config();
|
|
||||||
daemon_config.default_volume = Some(new_volume);
|
|
||||||
daemon_config.save_to_file().ok();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.app_state.volume_slider_value = self.audio_player_state.volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.request_repaint_after_secs(1.0 / 60.0);
|
ctx.request_repaint_after_secs(1.0 / 60.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+217
-94
@@ -8,6 +8,7 @@ use crate::{
|
|||||||
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source};
|
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
error::Error,
|
error::Error,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@@ -22,19 +23,35 @@ pub enum PlayerState {
|
|||||||
Playing,
|
Playing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TrackInfo {
|
||||||
|
pub id: u32,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub duration: Option<f32>,
|
||||||
|
pub position: f32,
|
||||||
|
pub volume: f32,
|
||||||
|
pub looped: bool,
|
||||||
|
pub paused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayingSound {
|
||||||
|
pub id: u32,
|
||||||
|
pub sink: Sink,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub duration: Option<f32>,
|
||||||
|
pub looped: bool,
|
||||||
|
pub volume: f32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AudioPlayer {
|
pub struct AudioPlayer {
|
||||||
_stream_handle: OutputStream,
|
pub stream_handle: OutputStream,
|
||||||
sink: Sink,
|
pub tracks: HashMap<u32, PlayingSound>,
|
||||||
|
pub next_id: u32,
|
||||||
|
|
||||||
input_link_sender: Option<pipewire::channel::Sender<Terminate>>,
|
input_link_sender: Option<pipewire::channel::Sender<Terminate>>,
|
||||||
pub current_input_device: Option<AudioDevice>,
|
pub current_input_device: Option<AudioDevice>,
|
||||||
|
|
||||||
pub volume: f32,
|
pub volume: f32, // Master volume
|
||||||
pub duration: Option<f32>,
|
|
||||||
|
|
||||||
pub current_file_path: Option<PathBuf>,
|
|
||||||
|
|
||||||
pub looped: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioPlayer {
|
impl AudioPlayer {
|
||||||
@@ -50,22 +67,16 @@ impl AudioPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stream_handle = OutputStreamBuilder::open_default_stream()?;
|
let stream_handle = OutputStreamBuilder::open_default_stream()?;
|
||||||
let sink = Sink::connect_new(stream_handle.mixer());
|
|
||||||
sink.set_volume(default_volume);
|
|
||||||
|
|
||||||
let mut audio_player = AudioPlayer {
|
let mut audio_player = AudioPlayer {
|
||||||
_stream_handle: stream_handle,
|
stream_handle,
|
||||||
sink,
|
tracks: HashMap::new(),
|
||||||
|
next_id: 1,
|
||||||
|
|
||||||
input_link_sender: None,
|
input_link_sender: None,
|
||||||
current_input_device: default_input_device.clone(),
|
current_input_device: default_input_device.clone(),
|
||||||
|
|
||||||
volume: default_volume,
|
volume: default_volume,
|
||||||
duration: None,
|
|
||||||
|
|
||||||
current_file_path: None,
|
|
||||||
|
|
||||||
looped: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if default_input_device.is_some() {
|
if default_input_device.is_some() {
|
||||||
@@ -132,74 +143,123 @@ impl AudioPlayer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pause(&mut self) {
|
pub fn pause(&mut self, id: Option<u32>) {
|
||||||
if self.get_state() == PlayerState::Playing {
|
if let Some(id) = id {
|
||||||
self.sink.pause();
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
}
|
sound.sink.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resume(&mut self) {
|
|
||||||
if self.get_state() == PlayerState::Paused {
|
|
||||||
self.sink.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
|
||||||
self.sink.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_paused(&self) -> bool {
|
|
||||||
self.sink.is_paused()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_state(&self) -> PlayerState {
|
|
||||||
if self.sink.len() == 0 {
|
|
||||||
return PlayerState::Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.sink.is_paused() {
|
|
||||||
return PlayerState::Paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerState::Playing
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_volume(&mut self, volume: f32) {
|
|
||||||
self.volume = volume;
|
|
||||||
self.sink.set_volume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position(&self) -> f32 {
|
|
||||||
if self.get_state() == PlayerState::Stopped {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.sink.get_pos().as_secs_f32()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seek(&mut self, mut position: f32) -> Result<(), Box<dyn Error>> {
|
|
||||||
if position < 0.0 {
|
|
||||||
position = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.sink.try_seek(Duration::from_secs_f32(position)) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(err) => Err(err.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_duration(&mut self) -> Result<f32, Box<dyn Error>> {
|
|
||||||
if self.get_state() == PlayerState::Stopped {
|
|
||||||
Err("Nothing is playing right now".into())
|
|
||||||
} else {
|
} else {
|
||||||
match self.duration {
|
for sound in self.tracks.values_mut() {
|
||||||
Some(duration) => Ok(duration),
|
sound.sink.pause();
|
||||||
None => Err("Couldn't determine duration for current file".into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn play(&mut self, file_path: &Path) -> Result<(), Box<dyn Error>> {
|
pub fn resume(&mut self, id: Option<u32>) {
|
||||||
|
if let Some(id) = id {
|
||||||
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
|
sound.sink.play();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for sound in self.tracks.values_mut() {
|
||||||
|
sound.sink.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self, id: Option<u32>) {
|
||||||
|
if let Some(id) = id {
|
||||||
|
self.tracks.remove(&id);
|
||||||
|
} else {
|
||||||
|
self.tracks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_paused(&self) -> bool {
|
||||||
|
if self.tracks.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.tracks.values().all(|s| s.sink.is_paused())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_state(&self) -> PlayerState {
|
||||||
|
if self.tracks.is_empty() {
|
||||||
|
return PlayerState::Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.tracks
|
||||||
|
.values()
|
||||||
|
.any(|s| !s.sink.is_paused() && !s.sink.empty())
|
||||||
|
{
|
||||||
|
return PlayerState::Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_paused() {
|
||||||
|
return PlayerState::Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerState::Stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_volume(&mut self, volume: f32, id: Option<u32>) {
|
||||||
|
if let Some(id) = id {
|
||||||
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
|
sound.volume = volume;
|
||||||
|
sound.sink.set_volume(self.volume * volume);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.volume = volume;
|
||||||
|
for sound in self.tracks.values_mut() {
|
||||||
|
sound.sink.set_volume(self.volume * sound.volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_position(&self, id: Option<u32>) -> f32 {
|
||||||
|
if let Some(id) = id {
|
||||||
|
if let Some(sound) = self.tracks.get(&id) {
|
||||||
|
return sound.sink.get_pos().as_secs_f32();
|
||||||
|
}
|
||||||
|
} else if let Some(sound) = self.tracks.values().last() {
|
||||||
|
// Fallback to last added track if no ID
|
||||||
|
return sound.sink.get_pos().as_secs_f32();
|
||||||
|
}
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seek(&mut self, position: f32, id: Option<u32>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let position = if position < 0.0 { 0.0 } else { position };
|
||||||
|
|
||||||
|
if let Some(id) = id {
|
||||||
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
|
sound.sink.try_seek(Duration::from_secs_f32(position))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Seek all? Or last? Let's seek all for now if no ID provided
|
||||||
|
for sound in self.tracks.values_mut() {
|
||||||
|
sound.sink.try_seek(Duration::from_secs_f32(position)).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_duration(&mut self, id: Option<u32>) -> Result<f32, Box<dyn Error>> {
|
||||||
|
if let Some(id) = id {
|
||||||
|
if let Some(sound) = self.tracks.get(&id) {
|
||||||
|
return sound.duration.ok_or("Unknown duration".into());
|
||||||
|
}
|
||||||
|
} else if let Some(sound) = self.tracks.values().last() {
|
||||||
|
return sound.duration.ok_or("Unknown duration".into());
|
||||||
|
}
|
||||||
|
Err("No track playing".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn play(
|
||||||
|
&mut self,
|
||||||
|
file_path: &Path,
|
||||||
|
concurrent: bool,
|
||||||
|
) -> Result<u32, Box<dyn Error>> {
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
return Err(format!("File does not exist: {}", file_path.display()).into());
|
return Err(format!("File does not exist: {}", file_path.display()).into());
|
||||||
}
|
}
|
||||||
@@ -207,30 +267,93 @@ impl AudioPlayer {
|
|||||||
let file = fs::File::open(file_path)?;
|
let file = fs::File::open(file_path)?;
|
||||||
match Decoder::try_from(file) {
|
match Decoder::try_from(file) {
|
||||||
Ok(source) => {
|
Ok(source) => {
|
||||||
self.current_file_path = Some(file_path.to_path_buf());
|
if !concurrent {
|
||||||
|
self.tracks.clear();
|
||||||
if let Some(duration) = source.total_duration() {
|
|
||||||
self.duration = Some(duration.as_secs_f32());
|
|
||||||
} else {
|
|
||||||
self.duration = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sink.stop();
|
let id = self.next_id;
|
||||||
self.sink.append(source);
|
self.next_id += 1;
|
||||||
self.sink.play();
|
|
||||||
|
let duration = source.total_duration().map(|d| d.as_secs_f32());
|
||||||
|
|
||||||
|
let sink = Sink::connect_new(self.stream_handle.mixer());
|
||||||
|
sink.set_volume(self.volume); // Default volume is 1.0 * master
|
||||||
|
sink.append(source);
|
||||||
|
sink.play();
|
||||||
|
|
||||||
|
let sound = PlayingSound {
|
||||||
|
id,
|
||||||
|
sink,
|
||||||
|
path: file_path.to_path_buf(),
|
||||||
|
duration,
|
||||||
|
looped: false,
|
||||||
|
volume: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tracks.insert(id, sound);
|
||||||
|
|
||||||
self.link_devices().await?;
|
self.link_devices().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(id)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_file_path(&mut self) -> &Option<PathBuf> {
|
pub fn set_loop(&mut self, enabled: bool, id: Option<u32>) {
|
||||||
if self.get_state() == PlayerState::Stopped && !self.looped {
|
if let Some(id) = id {
|
||||||
self.current_file_path = None;
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
|
sound.looped = enabled;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set loop for all? Or just last?
|
||||||
|
// Let's set for all.
|
||||||
|
for sound in self.tracks.values_mut() {
|
||||||
|
sound.looped = enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&self.current_file_path
|
}
|
||||||
|
|
||||||
|
pub fn get_tracks(&self) -> Vec<TrackInfo> {
|
||||||
|
let mut tracks: Vec<_> = self
|
||||||
|
.tracks
|
||||||
|
.values()
|
||||||
|
.map(|sound| TrackInfo {
|
||||||
|
id: sound.id,
|
||||||
|
path: sound.path.clone(),
|
||||||
|
duration: sound.duration,
|
||||||
|
position: sound.sink.get_pos().as_secs_f32(),
|
||||||
|
volume: sound.volume,
|
||||||
|
looped: sound.looped,
|
||||||
|
paused: sound.sink.is_paused(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
tracks.sort_by_key(|t| t.id);
|
||||||
|
tracks
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&mut self) {
|
||||||
|
let mut restarts = vec![];
|
||||||
|
|
||||||
|
for (id, sound) in &self.tracks {
|
||||||
|
if sound.sink.empty() && sound.looped {
|
||||||
|
restarts.push(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in restarts {
|
||||||
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
|
if let Ok(file) = fs::File::open(&sound.path) {
|
||||||
|
if let Ok(source) = Decoder::try_from(file) {
|
||||||
|
sound.sink.append(source);
|
||||||
|
sound.sink.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tracks
|
||||||
|
.retain(|_, sound| !sound.sink.empty() || sound.looped);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_current_input_device(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
|
pub async fn set_current_input_device(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
|||||||
+81
-43
@@ -12,13 +12,21 @@ pub trait Executable {
|
|||||||
|
|
||||||
pub struct PingCommand {}
|
pub struct PingCommand {}
|
||||||
|
|
||||||
pub struct PauseCommand {}
|
pub struct PauseCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ResumeCommand {}
|
pub struct ResumeCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TogglePauseCommand {}
|
pub struct TogglePauseCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct StopCommand {}
|
pub struct StopCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IsPausedCommand {}
|
pub struct IsPausedCommand {}
|
||||||
|
|
||||||
@@ -28,21 +36,28 @@ pub struct GetVolumeCommand {}
|
|||||||
|
|
||||||
pub struct SetVolumeCommand {
|
pub struct SetVolumeCommand {
|
||||||
pub volume: Option<f32>,
|
pub volume: Option<f32>,
|
||||||
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetPositionCommand {}
|
pub struct GetPositionCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SeekCommand {
|
pub struct SeekCommand {
|
||||||
pub position: Option<f32>,
|
pub position: Option<f32>,
|
||||||
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetDurationCommand {}
|
pub struct GetDurationCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PlayCommand {
|
pub struct PlayCommand {
|
||||||
pub file_path: Option<PathBuf>,
|
pub file_path: Option<PathBuf>,
|
||||||
|
pub concurrent: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetCurrentFilePathCommand {}
|
pub struct GetTracksCommand {}
|
||||||
|
|
||||||
pub struct GetCurrentInputCommand {}
|
pub struct GetCurrentInputCommand {}
|
||||||
|
|
||||||
@@ -52,13 +67,14 @@ pub struct SetCurrentInputCommand {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetLoopCommand {}
|
|
||||||
|
|
||||||
pub struct SetLoopCommand {
|
pub struct SetLoopCommand {
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToggleLoopCommand {}
|
pub struct ToggleLoopCommand {
|
||||||
|
pub id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for PingCommand {
|
impl Executable for PingCommand {
|
||||||
@@ -71,7 +87,7 @@ impl Executable for PingCommand {
|
|||||||
impl Executable for PauseCommand {
|
impl Executable for PauseCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
audio_player.pause();
|
audio_player.pause(self.id);
|
||||||
Response::new(true, "Audio was paused")
|
Response::new(true, "Audio was paused")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +96,7 @@ impl Executable for PauseCommand {
|
|||||||
impl Executable for ResumeCommand {
|
impl Executable for ResumeCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
audio_player.resume();
|
audio_player.resume(self.id);
|
||||||
Response::new(true, "Audio was resumed")
|
Response::new(true, "Audio was resumed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,12 +110,31 @@ impl Executable for TogglePauseCommand {
|
|||||||
return Response::new(false, "Audio is not playing");
|
return Response::new(false, "Audio is not playing");
|
||||||
}
|
}
|
||||||
|
|
||||||
if audio_player.is_paused() {
|
// This logic is a bit tricky with multiple tracks.
|
||||||
audio_player.resume();
|
// If ID is provided, toggle that track.
|
||||||
Response::new(true, "Audio was resumed")
|
// If not, toggle global pause state?
|
||||||
|
// For now, let's just use pause/resume based on global state if no ID.
|
||||||
|
|
||||||
|
if let Some(id) = self.id {
|
||||||
|
if let Some(track) = audio_player.tracks.get(&id) {
|
||||||
|
if track.sink.is_paused() {
|
||||||
|
audio_player.resume(Some(id));
|
||||||
|
Response::new(true, "Audio was resumed")
|
||||||
|
} else {
|
||||||
|
audio_player.pause(Some(id));
|
||||||
|
Response::new(true, "Audio was paused")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Response::new(false, "Track not found")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
audio_player.pause();
|
if audio_player.is_paused() {
|
||||||
Response::new(true, "Audio was paused")
|
audio_player.resume(None);
|
||||||
|
Response::new(true, "Audio was resumed")
|
||||||
|
} else {
|
||||||
|
audio_player.pause(None);
|
||||||
|
Response::new(true, "Audio was paused")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +143,7 @@ impl Executable for TogglePauseCommand {
|
|||||||
impl Executable for StopCommand {
|
impl Executable for StopCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
audio_player.stop();
|
audio_player.stop(self.id);
|
||||||
Response::new(true, "Audio was stopped")
|
Response::new(true, "Audio was stopped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +180,7 @@ impl Executable for SetVolumeCommand {
|
|||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
if let Some(volume) = self.volume {
|
if let Some(volume) = self.volume {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
audio_player.set_volume(volume);
|
audio_player.set_volume(volume, self.id);
|
||||||
Response::new(true, format!("Audio volume was set to {}", volume))
|
Response::new(true, format!("Audio volume was set to {}", volume))
|
||||||
} else {
|
} else {
|
||||||
Response::new(false, "Invalid volume value")
|
Response::new(false, "Invalid volume value")
|
||||||
@@ -157,7 +192,7 @@ impl Executable for SetVolumeCommand {
|
|||||||
impl Executable for GetPositionCommand {
|
impl Executable for GetPositionCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let audio_player = get_audio_player().await.lock().await;
|
let audio_player = get_audio_player().await.lock().await;
|
||||||
let position = audio_player.get_position();
|
let position = audio_player.get_position(self.id);
|
||||||
Response::new(true, position.to_string())
|
Response::new(true, position.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +202,7 @@ impl Executable for SeekCommand {
|
|||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
if let Some(position) = self.position {
|
if let Some(position) = self.position {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
match audio_player.seek(position) {
|
match audio_player.seek(position, self.id) {
|
||||||
Ok(_) => Response::new(true, format!("Audio position was set to {}", position)),
|
Ok(_) => Response::new(true, format!("Audio position was set to {}", position)),
|
||||||
Err(err) => Response::new(false, err.to_string()),
|
Err(err) => Response::new(false, err.to_string()),
|
||||||
}
|
}
|
||||||
@@ -181,7 +216,7 @@ impl Executable for SeekCommand {
|
|||||||
impl Executable for GetDurationCommand {
|
impl Executable for GetDurationCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
match audio_player.get_duration() {
|
match audio_player.get_duration(self.id) {
|
||||||
Ok(duration) => Response::new(true, duration.to_string()),
|
Ok(duration) => Response::new(true, duration.to_string()),
|
||||||
Err(err) => Response::new(false, err.to_string()),
|
Err(err) => Response::new(false, err.to_string()),
|
||||||
}
|
}
|
||||||
@@ -193,8 +228,11 @@ impl Executable for PlayCommand {
|
|||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
if let Some(file_path) = &self.file_path {
|
if let Some(file_path) = &self.file_path {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
match audio_player.play(file_path).await {
|
match audio_player
|
||||||
Ok(_) => Response::new(true, format!("Now playing {}", file_path.display())),
|
.play(file_path, self.concurrent.unwrap_or(false))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(id) => Response::new(true, id.to_string()),
|
||||||
Err(err) => Response::new(false, err.to_string()),
|
Err(err) => Response::new(false, err.to_string()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -204,15 +242,11 @@ impl Executable for PlayCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for GetCurrentFilePathCommand {
|
impl Executable for GetTracksCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let audio_player = get_audio_player().await.lock().await;
|
||||||
let current_file_path = audio_player.get_current_file_path();
|
let tracks = audio_player.get_tracks();
|
||||||
if let Some(current_file_path) = current_file_path {
|
Response::new(true, serde_json::to_string(&tracks).unwrap())
|
||||||
Response::new(true, current_file_path.to_str().unwrap())
|
|
||||||
} else {
|
|
||||||
Response::new(false, "No file is playing")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,14 +299,6 @@ impl Executable for SetCurrentInputCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Executable for GetLoopCommand {
|
|
||||||
async fn execute(&self) -> Response {
|
|
||||||
let audio_player = get_audio_player().await.lock().await;
|
|
||||||
Response::new(true, audio_player.looped.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for SetLoopCommand {
|
impl Executable for SetLoopCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -280,7 +306,7 @@ impl Executable for SetLoopCommand {
|
|||||||
|
|
||||||
match self.enabled {
|
match self.enabled {
|
||||||
Some(enabled) => {
|
Some(enabled) => {
|
||||||
audio_player.looped = enabled;
|
audio_player.set_loop(enabled, self.id);
|
||||||
Response::new(true, format!("Loop was set to {}", enabled))
|
Response::new(true, format!("Loop was set to {}", enabled))
|
||||||
}
|
}
|
||||||
None => Response::new(false, "Invalid enabled value"),
|
None => Response::new(false, "Invalid enabled value"),
|
||||||
@@ -292,7 +318,19 @@ impl Executable for SetLoopCommand {
|
|||||||
impl Executable for ToggleLoopCommand {
|
impl Executable for ToggleLoopCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
let mut audio_player = get_audio_player().await.lock().await;
|
let mut audio_player = get_audio_player().await.lock().await;
|
||||||
audio_player.looped = !audio_player.looped;
|
if let Some(id) = self.id {
|
||||||
Response::new(true, format!("Loop was set to {}", audio_player.looped))
|
if let Some(track) = audio_player.tracks.get_mut(&id) {
|
||||||
|
track.looped = !track.looped;
|
||||||
|
Response::new(true, format!("Loop was set to {}", track.looped))
|
||||||
|
} else {
|
||||||
|
Response::new(false, "Track not found")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Toggle all?
|
||||||
|
for track in audio_player.tracks.values_mut() {
|
||||||
|
track.looped = !track.looped;
|
||||||
|
}
|
||||||
|
Response::new(true, "Loop toggled for all tracks")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-12
@@ -1,23 +1,37 @@
|
|||||||
use crate::types::audio_player::PlayerState;
|
use crate::types::audio_player::{PlayerState, TrackInfo};
|
||||||
|
|
||||||
use egui::Id;
|
use egui::Id;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct AppState {
|
pub struct TrackUiState {
|
||||||
pub search_query: String,
|
|
||||||
|
|
||||||
pub position_slider_value: f32,
|
pub position_slider_value: f32,
|
||||||
pub volume_slider_value: f32,
|
pub volume_slider_value: f32,
|
||||||
|
|
||||||
pub position_dragged: bool,
|
pub position_dragged: bool,
|
||||||
pub volume_dragged: bool,
|
pub volume_dragged: bool,
|
||||||
|
|
||||||
|
pub ignore_position_update_until: Option<Instant>,
|
||||||
|
pub ignore_volume_update_until: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub search_query: String,
|
||||||
|
|
||||||
|
pub track_ui_states: HashMap<u32, TrackUiState>,
|
||||||
|
|
||||||
pub show_settings: bool,
|
pub show_settings: bool,
|
||||||
|
pub volume_dragged: bool,
|
||||||
|
|
||||||
|
pub volume_slider_value: f32,
|
||||||
|
|
||||||
|
pub ignore_volume_update_until: Option<Instant>,
|
||||||
|
|
||||||
pub current_dir: Option<PathBuf>,
|
pub current_dir: Option<PathBuf>,
|
||||||
pub dirs: HashSet<PathBuf>,
|
pub dirs: HashSet<PathBuf>,
|
||||||
@@ -33,17 +47,13 @@ pub struct AppState {
|
|||||||
pub struct AudioPlayerState {
|
pub struct AudioPlayerState {
|
||||||
pub state: PlayerState,
|
pub state: PlayerState,
|
||||||
pub new_state: Option<PlayerState>,
|
pub new_state: Option<PlayerState>,
|
||||||
pub current_file_path: PathBuf,
|
|
||||||
|
|
||||||
pub is_paused: bool,
|
pub tracks: Vec<TrackInfo>,
|
||||||
pub looped: bool,
|
|
||||||
|
|
||||||
pub volume: f32,
|
pub volume: f32, // Master volume
|
||||||
pub new_volume: Option<f32>,
|
|
||||||
pub position: f32,
|
|
||||||
pub new_position: Option<f32>,
|
|
||||||
pub duration: f32,
|
|
||||||
|
|
||||||
pub current_input: String,
|
pub current_input: String,
|
||||||
pub all_inputs: HashMap<String, String>,
|
pub all_inputs: HashMap<String, String>,
|
||||||
|
|
||||||
|
pub is_daemon_running: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-28
@@ -24,24 +24,54 @@ impl Request {
|
|||||||
Request::new("ping", vec![])
|
Request::new("ping", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pause() -> Self {
|
pub fn pause(id: Option<u32>) -> Self {
|
||||||
Request::new("pause", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("pause", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resume() -> Self {
|
pub fn resume(id: Option<u32>) -> Self {
|
||||||
Request::new("resume", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("resume", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_pause() -> Self {
|
pub fn toggle_pause(id: Option<u32>) -> Self {
|
||||||
Request::new("toggle_pause", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("toggle_pause", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop() -> Self {
|
pub fn stop(id: Option<u32>) -> Self {
|
||||||
Request::new("stop", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("stop", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(file_path: &str) -> Self {
|
pub fn play(file_path: &str, concurrent: bool) -> Self {
|
||||||
Request::new("play", vec![("file_path", file_path)])
|
Request::new(
|
||||||
|
"play",
|
||||||
|
vec![
|
||||||
|
("file_path", file_path),
|
||||||
|
("concurrent", &concurrent.to_string()),
|
||||||
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_is_paused() -> Self {
|
pub fn get_is_paused() -> Self {
|
||||||
@@ -52,20 +82,32 @@ impl Request {
|
|||||||
Request::new("get_volume", vec![])
|
Request::new("get_volume", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_position() -> Self {
|
pub fn get_position(id: Option<u32>) -> Self {
|
||||||
Request::new("get_position", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("get_position", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_duration() -> Self {
|
pub fn get_duration(id: Option<u32>) -> Self {
|
||||||
Request::new("get_duration", vec![])
|
let mut args = vec![];
|
||||||
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
|
id_str = id.to_string();
|
||||||
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("get_duration", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state() -> Self {
|
pub fn get_state() -> Self {
|
||||||
Request::new("get_state", vec![])
|
Request::new("get_state", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_file_path() -> Self {
|
pub fn get_tracks() -> Self {
|
||||||
Request::new("get_current_file_path", vec![])
|
Request::new("get_tracks", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_input() -> Self {
|
pub fn get_input() -> Self {
|
||||||
@@ -76,28 +118,42 @@ impl Request {
|
|||||||
Request::new("get_inputs", vec![])
|
Request::new("get_inputs", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_volume(volume: f32) -> Self {
|
pub fn set_volume(volume: f32, id: Option<u32>) -> Self {
|
||||||
Request::new("set_volume", vec![("volume", &volume.to_string())])
|
let mut args = vec![("volume".to_string(), volume.to_string())];
|
||||||
|
if let Some(id) = id {
|
||||||
|
args.push(("id".to_string(), id.to_string()));
|
||||||
|
}
|
||||||
|
Request::new("set_volume".to_string(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seek(position: f32) -> Self {
|
pub fn seek(position: f32, id: Option<u32>) -> Self {
|
||||||
Request::new("seek", vec![("position", &position.to_string())])
|
let mut args = vec![("position".to_string(), position.to_string())];
|
||||||
|
if let Some(id) = id {
|
||||||
|
args.push(("id".to_string(), id.to_string()));
|
||||||
|
}
|
||||||
|
Request::new("seek".to_string(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_input(name: &str) -> Self {
|
pub fn set_input(name: &str) -> Self {
|
||||||
Request::new("set_input", vec![("input_name", name)])
|
Request::new("set_input", vec![("input_name", name)])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_loop() -> Self {
|
pub fn set_loop(enabled: &str, id: Option<u32>) -> Self {
|
||||||
Request::new("get_loop", vec![])
|
let mut args = vec![("enabled".to_string(), enabled.to_string())];
|
||||||
|
if let Some(id) = id {
|
||||||
|
args.push(("id".to_string(), id.to_string()));
|
||||||
|
}
|
||||||
|
Request::new("set_loop".to_string(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_loop(enabled: &str) -> Self {
|
pub fn toggle_loop(id: Option<u32>) -> Self {
|
||||||
Request::new("set_loop", vec![("enabled", enabled)])
|
let mut args = vec![];
|
||||||
}
|
let id_str;
|
||||||
|
if let Some(id) = id {
|
||||||
pub fn toggle_loop() -> Self {
|
id_str = id.to_string();
|
||||||
Request::new("toggle_loop", vec![])
|
args.push(("id", id_str.as_str()));
|
||||||
|
}
|
||||||
|
Request::new("toggle_loop", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-13
@@ -3,12 +3,14 @@ use crate::types::{commands::*, socket::Request};
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
||||||
|
let id = request.args.get("id").and_then(|s| s.parse::<u32>().ok());
|
||||||
|
|
||||||
match request.name.as_str() {
|
match request.name.as_str() {
|
||||||
"ping" => Some(Box::new(PingCommand {})),
|
"ping" => Some(Box::new(PingCommand {})),
|
||||||
"pause" => Some(Box::new(PauseCommand {})),
|
"pause" => Some(Box::new(PauseCommand { id })),
|
||||||
"resume" => Some(Box::new(ResumeCommand {})),
|
"resume" => Some(Box::new(ResumeCommand { id })),
|
||||||
"toggle_pause" => Some(Box::new(TogglePauseCommand {})),
|
"toggle_pause" => Some(Box::new(TogglePauseCommand { id })),
|
||||||
"stop" => Some(Box::new(StopCommand {})),
|
"stop" => Some(Box::new(StopCommand { id })),
|
||||||
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
||||||
"get_state" => Some(Box::new(GetStateCommand {})),
|
"get_state" => Some(Box::new(GetStateCommand {})),
|
||||||
"get_volume" => Some(Box::new(GetVolumeCommand {})),
|
"get_volume" => Some(Box::new(GetVolumeCommand {})),
|
||||||
@@ -19,9 +21,9 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.unwrap_or(&String::new())
|
.unwrap_or(&String::new())
|
||||||
.parse::<f32>()
|
.parse::<f32>()
|
||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(SetVolumeCommand { volume }))
|
Some(Box::new(SetVolumeCommand { volume, id }))
|
||||||
}
|
}
|
||||||
"get_position" => Some(Box::new(GetPositionCommand {})),
|
"get_position" => Some(Box::new(GetPositionCommand { id })),
|
||||||
"seek" => {
|
"seek" => {
|
||||||
let position = request
|
let position = request
|
||||||
.args
|
.args
|
||||||
@@ -29,9 +31,9 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.unwrap_or(&String::new())
|
.unwrap_or(&String::new())
|
||||||
.parse::<f32>()
|
.parse::<f32>()
|
||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(SeekCommand { position }))
|
Some(Box::new(SeekCommand { position, id }))
|
||||||
}
|
}
|
||||||
"get_duration" => Some(Box::new(GetDurationCommand {})),
|
"get_duration" => Some(Box::new(GetDurationCommand { id })),
|
||||||
"play" => {
|
"play" => {
|
||||||
let file_path = request
|
let file_path = request
|
||||||
.args
|
.args
|
||||||
@@ -39,16 +41,24 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.unwrap_or(&String::new())
|
.unwrap_or(&String::new())
|
||||||
.parse::<PathBuf>()
|
.parse::<PathBuf>()
|
||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(PlayCommand { file_path }))
|
let concurrent = request
|
||||||
|
.args
|
||||||
|
.get("concurrent")
|
||||||
|
.unwrap_or(&String::new())
|
||||||
|
.parse::<bool>()
|
||||||
|
.ok();
|
||||||
|
Some(Box::new(PlayCommand {
|
||||||
|
file_path,
|
||||||
|
concurrent,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
"get_current_file_path" => Some(Box::new(GetCurrentFilePathCommand {})),
|
"get_tracks" => Some(Box::new(GetTracksCommand {})),
|
||||||
"get_input" => Some(Box::new(GetCurrentInputCommand {})),
|
"get_input" => Some(Box::new(GetCurrentInputCommand {})),
|
||||||
"get_inputs" => Some(Box::new(GetAllInputsCommand {})),
|
"get_inputs" => Some(Box::new(GetAllInputsCommand {})),
|
||||||
"set_input" => {
|
"set_input" => {
|
||||||
let name = Some(request.args.get("input_name").unwrap_or(&String::new())).cloned();
|
let name = Some(request.args.get("input_name").unwrap_or(&String::new())).cloned();
|
||||||
Some(Box::new(SetCurrentInputCommand { name }))
|
Some(Box::new(SetCurrentInputCommand { name }))
|
||||||
}
|
}
|
||||||
"get_loop" => Some(Box::new(GetLoopCommand {})),
|
|
||||||
"set_loop" => {
|
"set_loop" => {
|
||||||
let enabled = request
|
let enabled = request
|
||||||
.args
|
.args
|
||||||
@@ -56,9 +66,9 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.unwrap_or(&String::new())
|
.unwrap_or(&String::new())
|
||||||
.parse::<bool>()
|
.parse::<bool>()
|
||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(SetLoopCommand { enabled }))
|
Some(Box::new(SetLoopCommand { enabled, id }))
|
||||||
}
|
}
|
||||||
"toggle_loop" => Some(Box::new(ToggleLoopCommand {})),
|
"toggle_loop" => Some(Box::new(ToggleLoopCommand { id })),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-67
@@ -1,16 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::PlayerState,
|
audio_player::{PlayerState, TrackInfo},
|
||||||
config::GuiConfig,
|
config::GuiConfig,
|
||||||
gui::AudioPlayerState,
|
gui::AudioPlayerState,
|
||||||
socket::{Request, Response},
|
socket::{Request, Response},
|
||||||
},
|
},
|
||||||
utils::daemon::{make_request, wait_for_daemon},
|
utils::daemon::{is_daemon_running, make_request},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
error::Error,
|
error::Error,
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use tokio::time::{Duration, sleep};
|
use tokio::time::{Duration, sleep};
|
||||||
@@ -31,6 +30,12 @@ pub fn make_request_sync(request: Request) -> Result<Response, Box<dyn Error>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_request_async(request: Request) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
make_request(request).await.ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format_time_pair(position: f32, duration: f32) -> String {
|
pub fn format_time_pair(position: f32, duration: f32) -> String {
|
||||||
fn format_time(seconds: f32) -> String {
|
fn format_time(seconds: f32) -> String {
|
||||||
let total_seconds = seconds.round() as u32;
|
let total_seconds = seconds.round() as u32;
|
||||||
@@ -47,75 +52,54 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
|||||||
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
|
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
wait_for_daemon().await.ok();
|
let is_running = is_daemon_running().unwrap_or(false);
|
||||||
|
|
||||||
|
if !is_running {
|
||||||
|
{
|
||||||
|
let mut guard = audio_player_state_shared.lock().unwrap();
|
||||||
|
guard.is_daemon_running = false;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let state_req = Request::get_state();
|
let state_req = Request::get_state();
|
||||||
let file_path_req = Request::get_current_file_path();
|
let tracks_req = Request::get_tracks();
|
||||||
let is_paused_req = Request::get_is_paused();
|
|
||||||
let volume_req = Request::get_volume();
|
let volume_req = Request::get_volume();
|
||||||
let position_req = Request::get_position();
|
|
||||||
let duration_req = Request::get_duration();
|
|
||||||
let current_input_req = Request::get_input();
|
let current_input_req = Request::get_input();
|
||||||
let all_inputs_req = Request::get_inputs();
|
let all_inputs_req = Request::get_inputs();
|
||||||
let looped_req = Request::get_loop();
|
|
||||||
|
|
||||||
let (
|
let (state_res, tracks_res, volume_res, current_input_res, all_inputs_res) = tokio::join!(
|
||||||
state_res,
|
|
||||||
file_path_res,
|
|
||||||
is_paused_res,
|
|
||||||
volume_res,
|
|
||||||
position_res,
|
|
||||||
duration_res,
|
|
||||||
current_input_res,
|
|
||||||
all_inputs_res,
|
|
||||||
looped_res,
|
|
||||||
) = tokio::join!(
|
|
||||||
make_request(state_req),
|
make_request(state_req),
|
||||||
make_request(file_path_req),
|
make_request(tracks_req),
|
||||||
make_request(is_paused_req),
|
|
||||||
make_request(volume_req),
|
make_request(volume_req),
|
||||||
make_request(position_req),
|
|
||||||
make_request(duration_req),
|
|
||||||
make_request(current_input_req),
|
make_request(current_input_req),
|
||||||
make_request(all_inputs_req),
|
make_request(all_inputs_req),
|
||||||
make_request(looped_req),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let state_res = state_res.unwrap_or_default();
|
let state_res = state_res.unwrap_or_default();
|
||||||
let file_path_res = file_path_res.unwrap_or_default();
|
let tracks_res = tracks_res.unwrap_or_default();
|
||||||
let is_paused_res = is_paused_res.unwrap_or_default();
|
|
||||||
let volume_res = volume_res.unwrap_or_default();
|
let volume_res = volume_res.unwrap_or_default();
|
||||||
let position_res = position_res.unwrap_or_default();
|
|
||||||
let duration_res = duration_res.unwrap_or_default();
|
|
||||||
let current_input_res = current_input_res.unwrap_or_default();
|
let current_input_res = current_input_res.unwrap_or_default();
|
||||||
let all_inputs_res = all_inputs_res.unwrap_or_default();
|
let all_inputs_res = all_inputs_res.unwrap_or_default();
|
||||||
let looped_res = looped_res.unwrap_or_default();
|
|
||||||
|
|
||||||
let state = match state_res.status {
|
let state = match state_res.status {
|
||||||
true => serde_json::from_str::<PlayerState>(&state_res.message).unwrap(),
|
true => serde_json::from_str::<PlayerState>(&state_res.message).unwrap(),
|
||||||
false => PlayerState::default(),
|
false => PlayerState::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_path = match file_path_res.status {
|
let tracks = match tracks_res.status {
|
||||||
true => PathBuf::from(file_path_res.message),
|
true => {
|
||||||
false => PathBuf::new(),
|
serde_json::from_str::<Vec<TrackInfo>>(&tracks_res.message).unwrap_or_default()
|
||||||
};
|
}
|
||||||
let is_paused = match is_paused_res.status {
|
false => vec![],
|
||||||
true => is_paused_res.message == "true",
|
|
||||||
false => false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let volume = match volume_res.status {
|
let volume = match volume_res.status {
|
||||||
true => volume_res.message.parse::<f32>().unwrap(),
|
true => volume_res.message.parse::<f32>().unwrap(),
|
||||||
false => 0.0,
|
false => 0.0,
|
||||||
};
|
};
|
||||||
let position = match position_res.status {
|
|
||||||
true => position_res.message.parse::<f32>().unwrap(),
|
|
||||||
false => 0.0,
|
|
||||||
};
|
|
||||||
let duration = match duration_res.status {
|
|
||||||
true => duration_res.message.parse::<f32>().unwrap(),
|
|
||||||
false => 0.0,
|
|
||||||
};
|
|
||||||
let current_input = match current_input_res.status {
|
let current_input = match current_input_res.status {
|
||||||
true => current_input_res
|
true => current_input_res
|
||||||
.message
|
.message
|
||||||
@@ -144,10 +128,6 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
|||||||
.collect::<HashMap<String, String>>(),
|
.collect::<HashMap<String, String>>(),
|
||||||
false => HashMap::new(),
|
false => HashMap::new(),
|
||||||
};
|
};
|
||||||
let looped = match looped_res.status {
|
|
||||||
true => looped_res.message.parse::<bool>().unwrap_or_default(),
|
|
||||||
false => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut guard = audio_player_state_shared.lock().unwrap();
|
let mut guard = audio_player_state_shared.lock().unwrap();
|
||||||
@@ -159,26 +139,11 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
|||||||
}
|
}
|
||||||
None => state,
|
None => state,
|
||||||
};
|
};
|
||||||
guard.current_file_path = file_path;
|
guard.tracks = tracks.clone();
|
||||||
guard.is_paused = is_paused;
|
guard.volume = volume;
|
||||||
guard.volume = match guard.new_volume {
|
|
||||||
Some(new_volume) => {
|
|
||||||
guard.new_volume = None;
|
|
||||||
new_volume
|
|
||||||
}
|
|
||||||
None => volume,
|
|
||||||
};
|
|
||||||
guard.position = match guard.new_position {
|
|
||||||
Some(new_position) => {
|
|
||||||
guard.new_position = None;
|
|
||||||
new_position
|
|
||||||
}
|
|
||||||
None => position,
|
|
||||||
};
|
|
||||||
guard.duration = if duration > 0.0 { duration } else { 1.0 };
|
|
||||||
guard.current_input = current_input;
|
guard.current_input = current_input;
|
||||||
guard.all_inputs = all_inputs;
|
guard.all_inputs = all_inputs;
|
||||||
guard.looped = looped;
|
guard.is_daemon_running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(sleep_duration).await;
|
sleep(sleep_duration).await;
|
||||||
|
|||||||
@@ -165,8 +165,8 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
|
|||||||
input_device.input_fr = Some(port)
|
input_device.input_fr = Some(port)
|
||||||
}
|
}
|
||||||
"capture_MONO" => {
|
"capture_MONO" => {
|
||||||
input_device.input_fl = Some(port.clone());
|
input_device.output_fl = Some(port.clone());
|
||||||
input_device.input_fr = Some(port);
|
input_device.output_fr = Some(port);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user