Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

chromecast: handle chromecast connect/disconnect

+1897 -106
+268 -64
Cargo.lock
··· 148 148 checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 149 149 dependencies = [ 150 150 "bytestring", 151 - "cfg-if", 151 + "cfg-if 1.0.0", 152 152 "http 0.2.12", 153 153 "regex", 154 154 "regex-lite", ··· 178 178 "futures-core", 179 179 "futures-util", 180 180 "mio 1.0.2", 181 - "socket2", 181 + "socket2 0.5.7", 182 182 "tokio", 183 183 "tracing", 184 184 ] ··· 222 222 "ahash", 223 223 "bytes", 224 224 "bytestring", 225 - "cfg-if", 225 + "cfg-if 1.0.0", 226 226 "cookie", 227 227 "derive_more", 228 228 "encoding_rs", ··· 241 241 "serde_json", 242 242 "serde_urlencoded", 243 243 "smallvec", 244 - "socket2", 244 + "socket2 0.5.7", 245 245 "time", 246 246 "url", 247 247 ] ··· 338 338 source = "registry+https://github.com/rust-lang/crates.io-index" 339 339 checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" 340 340 dependencies = [ 341 - "cfg-if", 341 + "cfg-if 1.0.0", 342 342 "cipher", 343 343 "cpufeatures", 344 344 ] ··· 372 372 source = "registry+https://github.com/rust-lang/crates.io-index" 373 373 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 374 374 dependencies = [ 375 - "cfg-if", 375 + "cfg-if 1.0.0", 376 376 "getrandom", 377 377 "once_cell", 378 378 "version_check", ··· 667 667 "async-graphql-derive", 668 668 "async-graphql-parser", 669 669 "async-graphql-value", 670 - "async-stream", 670 + "async-stream 0.3.6", 671 671 "async-trait", 672 672 "base64 0.22.1", 673 673 "bytes", ··· 704 704 "actix-web-actors", 705 705 "async-channel 2.3.1", 706 706 "async-graphql", 707 - "async-stream", 707 + "async-stream 0.3.6", 708 708 "futures-channel", 709 709 "futures-util", 710 710 "serde_json", ··· 759 759 checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" 760 760 dependencies = [ 761 761 "async-lock", 762 - "cfg-if", 762 + "cfg-if 1.0.0", 763 763 "concurrent-queue", 764 764 "futures-io", 765 765 "futures-lite", 766 766 "parking", 767 - "polling", 767 + "polling 3.7.3", 768 768 "rustix", 769 769 "slab", 770 770 "tracing", ··· 794 794 "async-signal", 795 795 "async-task", 796 796 "blocking", 797 - "cfg-if", 797 + "cfg-if 1.0.0", 798 798 "event-listener 5.3.1", 799 799 "futures-lite", 800 800 "rustix", ··· 821 821 "async-io", 822 822 "async-lock", 823 823 "atomic-waker", 824 - "cfg-if", 824 + "cfg-if 1.0.0", 825 825 "futures-core", 826 826 "futures-io", 827 827 "rustix", ··· 860 860 861 861 [[package]] 862 862 name = "async-stream" 863 - version = "0.3.5" 863 + version = "0.2.1" 864 + source = "registry+https://github.com/rust-lang/crates.io-index" 865 + checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" 866 + dependencies = [ 867 + "async-stream-impl 0.2.1", 868 + "futures-core", 869 + ] 870 + 871 + [[package]] 872 + name = "async-stream" 873 + version = "0.3.6" 864 874 source = "registry+https://github.com/rust-lang/crates.io-index" 865 - checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 875 + checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" 866 876 dependencies = [ 867 - "async-stream-impl", 877 + "async-stream-impl 0.3.6", 868 878 "futures-core", 869 879 "pin-project-lite", 870 880 ] 871 881 872 882 [[package]] 873 883 name = "async-stream-impl" 874 - version = "0.3.5" 884 + version = "0.2.1" 885 + source = "registry+https://github.com/rust-lang/crates.io-index" 886 + checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" 887 + dependencies = [ 888 + "proc-macro2", 889 + "quote", 890 + "syn 1.0.109", 891 + ] 892 + 893 + [[package]] 894 + name = "async-stream-impl" 895 + version = "0.3.6" 875 896 source = "registry+https://github.com/rust-lang/crates.io-index" 876 - checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 897 + checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" 877 898 dependencies = [ 878 899 "proc-macro2", 879 900 "quote", ··· 972 993 checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 973 994 dependencies = [ 974 995 "addr2line", 975 - "cfg-if", 996 + "cfg-if 1.0.0", 976 997 "libc", 977 998 "miniz_oxide 0.8.0", 978 999 "object", ··· 1294 1315 dependencies = [ 1295 1316 "nom 7.1.3", 1296 1317 ] 1318 + 1319 + [[package]] 1320 + name = "cfg-if" 1321 + version = "0.1.10" 1322 + source = "registry+https://github.com/rust-lang/crates.io-index" 1323 + checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 1297 1324 1298 1325 [[package]] 1299 1326 name = "cfg-if" ··· 1519 1546 source = "registry+https://github.com/rust-lang/crates.io-index" 1520 1547 checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 1521 1548 dependencies = [ 1522 - "cfg-if", 1549 + "cfg-if 1.0.0", 1523 1550 ] 1524 1551 1525 1552 [[package]] ··· 1644 1671 source = "registry+https://github.com/rust-lang/crates.io-index" 1645 1672 checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" 1646 1673 dependencies = [ 1647 - "cfg-if", 1674 + "cfg-if 1.0.0", 1648 1675 "cpufeatures", 1649 1676 "curve25519-dalek-derive", 1650 1677 "digest", ··· 1717 1744 source = "registry+https://github.com/rust-lang/crates.io-index" 1718 1745 checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 1719 1746 dependencies = [ 1720 - "cfg-if", 1747 + "cfg-if 1.0.0", 1721 1748 "hashbrown 0.14.5", 1722 1749 "lock_api", 1723 1750 "once_cell", ··· 2187 2214 "pin-project", 2188 2215 "rustls-tokio-stream", 2189 2216 "serde", 2190 - "socket2", 2217 + "socket2 0.5.7", 2191 2218 "tokio", 2192 2219 "trust-dns-proto", 2193 2220 "trust-dns-resolver", ··· 2606 2633 checksum = "29a6f771b606d61ec967beb479aca25e7d9c5f4d8ef4a0bdd8c54783bdac3c63" 2607 2634 dependencies = [ 2608 2635 "anyhow", 2609 - "async-stream", 2636 + "async-stream 0.3.6", 2610 2637 "async-trait", 2611 2638 "bytes", 2612 2639 "chrono", ··· 2631 2658 checksum = "3c4f5719e2bf698ec4f39fe29d91b62ff06a4b4a37ee481ffb8658d140fed986" 2632 2659 dependencies = [ 2633 2660 "anyhow", 2634 - "async-stream", 2661 + "async-stream 0.3.6", 2635 2662 "async-trait", 2636 2663 "chrono", 2637 2664 "denokv_proto", ··· 2768 2795 ] 2769 2796 2770 2797 [[package]] 2798 + name = "dns-parser" 2799 + version = "0.8.0" 2800 + source = "registry+https://github.com/rust-lang/crates.io-index" 2801 + checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" 2802 + dependencies = [ 2803 + "byteorder", 2804 + "quick-error", 2805 + ] 2806 + 2807 + [[package]] 2771 2808 name = "document-features" 2772 2809 version = "0.2.10" 2773 2810 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2939 2976 source = "registry+https://github.com/rust-lang/crates.io-index" 2940 2977 checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 2941 2978 dependencies = [ 2942 - "cfg-if", 2979 + "cfg-if 1.0.0", 2943 2980 ] 2944 2981 2945 2982 [[package]] ··· 2994 3031 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 2995 3032 2996 3033 [[package]] 3034 + name = "err-derive" 3035 + version = "0.2.4" 3036 + source = "registry+https://github.com/rust-lang/crates.io-index" 3037 + checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" 3038 + dependencies = [ 3039 + "proc-macro-error", 3040 + "proc-macro2", 3041 + "quote", 3042 + "rustversion", 3043 + "syn 1.0.109", 3044 + "synstructure 0.12.6", 3045 + ] 3046 + 3047 + [[package]] 2997 3048 name = "errno" 2998 3049 version = "0.2.8" 2999 3050 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3042 3093 source = "registry+https://github.com/rust-lang/crates.io-index" 3043 3094 checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 3044 3095 dependencies = [ 3045 - "cfg-if", 3096 + "cfg-if 1.0.0", 3046 3097 "home", 3047 3098 "windows-sys 0.48.0", 3048 3099 ] ··· 3164 3215 source = "registry+https://github.com/rust-lang/crates.io-index" 3165 3216 checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" 3166 3217 dependencies = [ 3167 - "cfg-if", 3218 + "cfg-if 1.0.0", 3168 3219 "rustix", 3169 3220 "windows-sys 0.52.0", 3170 3221 ] ··· 3200 3251 source = "registry+https://github.com/rust-lang/crates.io-index" 3201 3252 checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 3202 3253 dependencies = [ 3203 - "cfg-if", 3254 + "cfg-if 1.0.0", 3204 3255 "libc", 3205 3256 "libredox", 3206 3257 "windows-sys 0.59.0", ··· 3233 3284 3234 3285 [[package]] 3235 3286 name = "flume" 3287 + version = "0.10.14" 3288 + source = "registry+https://github.com/rust-lang/crates.io-index" 3289 + checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 3290 + dependencies = [ 3291 + "futures-core", 3292 + "futures-sink", 3293 + "pin-project", 3294 + "spin", 3295 + ] 3296 + 3297 + [[package]] 3298 + name = "flume" 3236 3299 version = "0.11.0" 3237 3300 source = "registry+https://github.com/rust-lang/crates.io-index" 3238 3301 checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" ··· 3489 3552 source = "registry+https://github.com/rust-lang/crates.io-index" 3490 3553 checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 3491 3554 dependencies = [ 3492 - "cfg-if", 3555 + "cfg-if 1.0.0", 3493 3556 "js-sys", 3494 3557 "libc", 3495 3558 "wasi", ··· 3799 3862 source = "registry+https://github.com/rust-lang/crates.io-index" 3800 3863 checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" 3801 3864 dependencies = [ 3802 - "cfg-if", 3865 + "cfg-if 1.0.0", 3803 3866 "libc", 3804 3867 "windows", 3805 3868 ] ··· 3915 3978 "httpdate", 3916 3979 "itoa", 3917 3980 "pin-project-lite", 3918 - "socket2", 3981 + "socket2 0.5.7", 3919 3982 "tokio", 3920 3983 "tower-service", 3921 3984 "tracing", ··· 3987 4050 "http-body 1.0.1", 3988 4051 "hyper 1.4.1", 3989 4052 "pin-project-lite", 3990 - "socket2", 4053 + "socket2 0.5.7", 3991 4054 "tokio", 3992 4055 "tower", 3993 4056 "tower-service", ··· 4044 4107 ] 4045 4108 4046 4109 [[package]] 4110 + name = "if-addrs" 4111 + version = "0.7.0" 4112 + source = "registry+https://github.com/rust-lang/crates.io-index" 4113 + checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" 4114 + dependencies = [ 4115 + "libc", 4116 + "winapi", 4117 + ] 4118 + 4119 + [[package]] 4120 + name = "if-addrs" 4121 + version = "0.12.0" 4122 + source = "registry+https://github.com/rust-lang/crates.io-index" 4123 + checksum = "bb2a33e9c38988ecbda730c85b0fd9ddcdf83c0305ac7fd21c8bb9f57f2f0cc8" 4124 + dependencies = [ 4125 + "libc", 4126 + "windows-sys 0.52.0", 4127 + ] 4128 + 4129 + [[package]] 4047 4130 name = "if_chain" 4048 4131 version = "1.0.2" 4049 4132 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4125 4208 source = "registry+https://github.com/rust-lang/crates.io-index" 4126 4209 checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 4127 4210 dependencies = [ 4128 - "cfg-if", 4211 + "cfg-if 1.0.0", 4129 4212 "js-sys", 4130 4213 "wasm-bindgen", 4131 4214 "web-sys", ··· 4137 4220 source = "registry+https://github.com/rust-lang/crates.io-index" 4138 4221 checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 4139 4222 dependencies = [ 4140 - "socket2", 4223 + "socket2 0.5.7", 4141 4224 "widestring", 4142 4225 "windows-sys 0.48.0", 4143 4226 "winreg", ··· 4249 4332 source = "registry+https://github.com/rust-lang/crates.io-index" 4250 4333 checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" 4251 4334 dependencies = [ 4252 - "cfg-if", 4335 + "cfg-if 1.0.0", 4253 4336 "ecdsa", 4254 4337 "elliptic-curve", 4255 4338 "once_cell", ··· 4467 4550 source = "registry+https://github.com/rust-lang/crates.io-index" 4468 4551 checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 4469 4552 dependencies = [ 4470 - "cfg-if", 4553 + "cfg-if 1.0.0", 4471 4554 "winapi", 4472 4555 ] 4473 4556 ··· 4477 4560 source = "registry+https://github.com/rust-lang/crates.io-index" 4478 4561 checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 4479 4562 dependencies = [ 4480 - "cfg-if", 4563 + "cfg-if 1.0.0", 4481 4564 "windows-targets 0.52.6", 4482 4565 ] 4483 4566 ··· 4488 4571 checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 4489 4572 4490 4573 [[package]] 4574 + name = "libmdns" 4575 + version = "0.9.1" 4576 + source = "registry+https://github.com/rust-lang/crates.io-index" 4577 + checksum = "48854699e11b111433431b69cee2365fcab0b29b06993f48c257dfbaf6395862" 4578 + dependencies = [ 4579 + "byteorder", 4580 + "futures-util", 4581 + "hostname 0.4.0", 4582 + "if-addrs 0.12.0", 4583 + "log", 4584 + "multimap 0.10.0", 4585 + "rand", 4586 + "socket2 0.5.7", 4587 + "thiserror", 4588 + "tokio", 4589 + ] 4590 + 4591 + [[package]] 4491 4592 name = "libredox" 4492 4593 version = "0.1.3" 4493 4594 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4547 4648 "futures-core", 4548 4649 "futures-sink", 4549 4650 "local-waker", 4651 + ] 4652 + 4653 + [[package]] 4654 + name = "local-ip-addr" 4655 + version = "0.1.1" 4656 + source = "registry+https://github.com/rust-lang/crates.io-index" 4657 + checksum = "cb5eb03bdac69ee369631cbb0131787688639f8192a017d5aeb98c51223dd514" 4658 + dependencies = [ 4659 + "anyhow", 4660 + "socket2 0.4.10", 4550 4661 ] 4551 4662 4552 4663 [[package]] ··· 4651 4762 source = "registry+https://github.com/rust-lang/crates.io-index" 4652 4763 checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 4653 4764 dependencies = [ 4654 - "cfg-if", 4765 + "cfg-if 1.0.0", 4655 4766 "digest", 4656 4767 ] 4657 4768 ··· 4671 4782 checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 4672 4783 4673 4784 [[package]] 4785 + name = "mdns" 4786 + version = "3.0.0" 4787 + source = "registry+https://github.com/rust-lang/crates.io-index" 4788 + checksum = "c769962ac75a6ea437f0922b27834bcccd4c013d591383a16ae5731e3ef0f3f3" 4789 + dependencies = [ 4790 + "async-std", 4791 + "async-stream 0.2.1", 4792 + "dns-parser", 4793 + "err-derive", 4794 + "futures-core", 4795 + "futures-util", 4796 + "log", 4797 + "net2", 4798 + ] 4799 + 4800 + [[package]] 4801 + name = "mdns-sd" 4802 + version = "0.5.10" 4803 + source = "registry+https://github.com/rust-lang/crates.io-index" 4804 + checksum = "709cba29c9d7334db28706bc2767db2531934fd2e55781cba82c930fb7f22b47" 4805 + dependencies = [ 4806 + "flume 0.10.14", 4807 + "if-addrs 0.7.0", 4808 + "log", 4809 + "polling 2.8.0", 4810 + "socket2 0.4.10", 4811 + ] 4812 + 4813 + [[package]] 4674 4814 name = "measure_time" 4675 4815 version = "0.8.3" 4676 4816 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4862 5002 version = "0.10.0" 4863 5003 source = "registry+https://github.com/rust-lang/crates.io-index" 4864 5004 checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" 5005 + dependencies = [ 5006 + "serde", 5007 + ] 4865 5008 4866 5009 [[package]] 4867 5010 name = "murmurhash32" ··· 4901 5044 ] 4902 5045 4903 5046 [[package]] 5047 + name = "net2" 5048 + version = "0.2.39" 5049 + source = "registry+https://github.com/rust-lang/crates.io-index" 5050 + checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" 5051 + dependencies = [ 5052 + "cfg-if 0.1.10", 5053 + "libc", 5054 + "winapi", 5055 + ] 5056 + 5057 + [[package]] 4904 5058 name = "netif" 4905 5059 version = "0.1.6" 4906 5060 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4932 5086 checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" 4933 5087 dependencies = [ 4934 5088 "bitflags 1.3.2", 4935 - "cfg-if", 5089 + "cfg-if 1.0.0", 4936 5090 "libc", 4937 5091 "memoffset 0.7.1", 4938 5092 "pin-utils", ··· 4946 5100 checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 4947 5101 dependencies = [ 4948 5102 "bitflags 2.6.0", 4949 - "cfg-if", 5103 + "cfg-if 1.0.0", 4950 5104 "libc", 4951 5105 ] 4952 5106 ··· 4957 5111 checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 4958 5112 dependencies = [ 4959 5113 "bitflags 2.6.0", 4960 - "cfg-if", 5114 + "cfg-if 1.0.0", 4961 5115 "cfg_aliases 0.2.1", 4962 5116 "libc", 4963 5117 "memoffset 0.9.1", ··· 5357 5511 source = "registry+https://github.com/rust-lang/crates.io-index" 5358 5512 checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 5359 5513 dependencies = [ 5360 - "cfg-if", 5514 + "cfg-if 1.0.0", 5361 5515 "libc", 5362 5516 "redox_syscall", 5363 5517 "smallvec", ··· 5617 5771 5618 5772 [[package]] 5619 5773 name = "polling" 5774 + version = "2.8.0" 5775 + source = "registry+https://github.com/rust-lang/crates.io-index" 5776 + checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 5777 + dependencies = [ 5778 + "autocfg", 5779 + "bitflags 1.3.2", 5780 + "cfg-if 1.0.0", 5781 + "concurrent-queue", 5782 + "libc", 5783 + "log", 5784 + "pin-project-lite", 5785 + "windows-sys 0.48.0", 5786 + ] 5787 + 5788 + [[package]] 5789 + name = "polling" 5620 5790 version = "3.7.3" 5621 5791 source = "registry+https://github.com/rust-lang/crates.io-index" 5622 5792 checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" 5623 5793 dependencies = [ 5624 - "cfg-if", 5794 + "cfg-if 1.0.0", 5625 5795 "concurrent-queue", 5626 5796 "hermit-abi 0.4.0", 5627 5797 "pin-project-lite", ··· 5636 5806 source = "registry+https://github.com/rust-lang/crates.io-index" 5637 5807 checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 5638 5808 dependencies = [ 5639 - "cfg-if", 5809 + "cfg-if 1.0.0", 5640 5810 "cpufeatures", 5641 5811 "opaque-debug", 5642 5812 "universal-hash", ··· 5975 6145 "quinn-udp", 5976 6146 "rustc-hash 2.0.0", 5977 6147 "rustls 0.23.13", 5978 - "socket2", 6148 + "socket2 0.5.7", 5979 6149 "thiserror", 5980 6150 "tokio", 5981 6151 "tracing", ··· 6006 6176 dependencies = [ 6007 6177 "libc", 6008 6178 "once_cell", 6009 - "socket2", 6179 + "socket2 0.5.7", 6010 6180 "tracing", 6011 6181 "windows-sys 0.52.0", 6012 6182 ] ··· 6242 6412 checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 6243 6413 dependencies = [ 6244 6414 "cc", 6245 - "cfg-if", 6415 + "cfg-if 1.0.0", 6246 6416 "getrandom", 6247 6417 "libc", 6248 6418 "spin", ··· 6309 6479 [[package]] 6310 6480 name = "rockbox-discovery" 6311 6481 version = "0.1.0" 6482 + dependencies = [ 6483 + "async-stream 0.3.6", 6484 + "futures-util", 6485 + "libmdns", 6486 + "mdns", 6487 + "mdns-sd", 6488 + "tokio", 6489 + ] 6312 6490 6313 6491 [[package]] 6314 6492 name = "rockbox-ext" ··· 6457 6635 dependencies = [ 6458 6636 "anyhow", 6459 6637 "async-std", 6638 + "futures-util", 6460 6639 "md5", 6461 6640 "owo-colors 4.1.0", 6462 6641 "queryst", 6463 6642 "rand", 6464 6643 "reqwest", 6644 + "rockbox-chromecast", 6645 + "rockbox-discovery", 6465 6646 "rockbox-graphql", 6466 6647 "rockbox-library", 6467 6648 "rockbox-mpd", ··· 6470 6651 "rockbox-search", 6471 6652 "rockbox-settings", 6472 6653 "rockbox-sys", 6654 + "rockbox-tracklist", 6655 + "rockbox-traits", 6473 6656 "rockbox-types", 6474 6657 "serde", 6475 6658 "serde_json", ··· 6497 6680 ] 6498 6681 6499 6682 [[package]] 6683 + name = "rockbox-tracklist" 6684 + version = "0.1.0" 6685 + dependencies = [ 6686 + "rand", 6687 + "rockbox-traits", 6688 + ] 6689 + 6690 + [[package]] 6500 6691 name = "rockbox-traits" 6501 6692 version = "0.1.0" 6502 6693 dependencies = [ ··· 6508 6699 name = "rockbox-types" 6509 6700 version = "0.1.0" 6510 6701 dependencies = [ 6702 + "local-ip-addr", 6703 + "mdns-sd", 6511 6704 "rockbox-search", 6705 + "rockbox-traits", 6512 6706 "serde", 6513 6707 ] 6514 6708 ··· 6736 6930 dependencies = [ 6737 6931 "futures", 6738 6932 "rustls 0.23.13", 6739 - "socket2", 6933 + "socket2 0.5.7", 6740 6934 "tokio", 6741 6935 ] 6742 6936 ··· 6774 6968 checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" 6775 6969 dependencies = [ 6776 6970 "bitflags 2.6.0", 6777 - "cfg-if", 6971 + "cfg-if 1.0.0", 6778 6972 "clipboard-win", 6779 6973 "fd-lock", 6780 6974 "home", ··· 7044 7238 source = "registry+https://github.com/rust-lang/crates.io-index" 7045 7239 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 7046 7240 dependencies = [ 7047 - "cfg-if", 7241 + "cfg-if 1.0.0", 7048 7242 "cpufeatures", 7049 7243 "digest", 7050 7244 ] ··· 7055 7249 source = "registry+https://github.com/rust-lang/crates.io-index" 7056 7250 checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 7057 7251 dependencies = [ 7058 - "cfg-if", 7252 + "cfg-if 1.0.0", 7059 7253 "cpufeatures", 7060 7254 "digest", 7061 7255 ] ··· 7206 7400 7207 7401 [[package]] 7208 7402 name = "socket2" 7403 + version = "0.4.10" 7404 + source = "registry+https://github.com/rust-lang/crates.io-index" 7405 + checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 7406 + dependencies = [ 7407 + "libc", 7408 + "winapi", 7409 + ] 7410 + 7411 + [[package]] 7412 + name = "socket2" 7209 7413 version = "0.5.7" 7210 7414 source = "registry+https://github.com/rust-lang/crates.io-index" 7211 7415 checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" ··· 7456 7660 dependencies = [ 7457 7661 "atoi", 7458 7662 "chrono", 7459 - "flume", 7663 + "flume 0.11.0", 7460 7664 "futures-channel", 7461 7665 "futures-core", 7462 7666 "futures-executor", ··· 7485 7689 checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" 7486 7690 dependencies = [ 7487 7691 "cc", 7488 - "cfg-if", 7692 + "cfg-if 1.0.0", 7489 7693 "libc", 7490 7694 "psm", 7491 7695 "windows-sys 0.59.0", ··· 7629 7833 dependencies = [ 7630 7834 "ast_node", 7631 7835 "better_scoped_tls", 7632 - "cfg-if", 7836 + "cfg-if 1.0.0", 7633 7837 "either", 7634 7838 "from_variant", 7635 7839 "new_debug_unreachable", ··· 8161 8365 source = "registry+https://github.com/rust-lang/crates.io-index" 8162 8366 checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 8163 8367 dependencies = [ 8164 - "cfg-if", 8368 + "cfg-if 1.0.0", 8165 8369 "fastrand", 8166 8370 "once_cell", 8167 8371 "rustix", ··· 8275 8479 "parking_lot", 8276 8480 "pin-project-lite", 8277 8481 "signal-hook-registry", 8278 - "socket2", 8482 + "socket2 0.5.7", 8279 8483 "tokio-macros", 8280 8484 "windows-sys 0.48.0", 8281 8485 ] ··· 8394 8598 source = "registry+https://github.com/rust-lang/crates.io-index" 8395 8599 checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" 8396 8600 dependencies = [ 8397 - "async-stream", 8601 + "async-stream 0.3.6", 8398 8602 "async-trait", 8399 8603 "axum", 8400 8604 "base64 0.22.1", ··· 8409 8613 "percent-encoding", 8410 8614 "pin-project", 8411 8615 "prost 0.13.2", 8412 - "socket2", 8616 + "socket2 0.5.7", 8413 8617 "tokio", 8414 8618 "tokio-stream", 8415 8619 "tower", ··· 8576 8780 checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" 8577 8781 dependencies = [ 8578 8782 "async-trait", 8579 - "cfg-if", 8783 + "cfg-if 1.0.0", 8580 8784 "data-encoding", 8581 8785 "enum-as-inner", 8582 8786 "futures-channel", ··· 8601 8805 source = "registry+https://github.com/rust-lang/crates.io-index" 8602 8806 checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" 8603 8807 dependencies = [ 8604 - "cfg-if", 8808 + "cfg-if 1.0.0", 8605 8809 "futures-util", 8606 8810 "ipconfig", 8607 8811 "lru-cache", ··· 8629 8833 source = "registry+https://github.com/rust-lang/crates.io-index" 8630 8834 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 8631 8835 dependencies = [ 8632 - "cfg-if", 8836 + "cfg-if 1.0.0", 8633 8837 "rand", 8634 8838 "static_assertions", 8635 8839 ] ··· 8961 9165 source = "registry+https://github.com/rust-lang/crates.io-index" 8962 9166 checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 8963 9167 dependencies = [ 8964 - "cfg-if", 9168 + "cfg-if 1.0.0", 8965 9169 "once_cell", 8966 9170 "wasm-bindgen-macro", 8967 9171 ] ··· 8987 9191 source = "registry+https://github.com/rust-lang/crates.io-index" 8988 9192 checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 8989 9193 dependencies = [ 8990 - "cfg-if", 9194 + "cfg-if 1.0.0", 8991 9195 "js-sys", 8992 9196 "wasm-bindgen", 8993 9197 "web-sys", ··· 9427 9631 source = "registry+https://github.com/rust-lang/crates.io-index" 9428 9632 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 9429 9633 dependencies = [ 9430 - "cfg-if", 9634 + "cfg-if 1.0.0", 9431 9635 "windows-sys 0.48.0", 9432 9636 ] 9433 9637
+205 -21
crates/chromecast/src/lib.rs
··· 12 12 use async_trait::async_trait; 13 13 use chromecast::{ 14 14 channels::{ 15 - media::{Metadata, StatusEntry}, 15 + media::{Image, Media, Metadata, MusicTrackMediaMetadata, StatusEntry, StreamType}, 16 16 receiver::CastDeviceApp, 17 17 }, 18 18 CastDevice, 19 19 }; 20 + use rockbox_traits::types::track::{Album, Track}; 21 + use rockbox_traits::types::{playback::Playback, track::Artist}; 20 22 use rockbox_traits::Player; 21 23 use rockbox_types::device::Device; 22 24 use tokio::sync::mpsc; 23 - use types::track::{Album, Track}; 24 - use types::{playback::Playback, track::Artist}; 25 25 26 26 const DEFAULT_DESTINATION_ID: &str = "receiver-0"; 27 27 const DEFAULT_APP_ID: &str = "88DCBD57"; 28 - 29 - pub mod types; 30 - 31 28 pub struct Chromecast<'a> { 32 29 host: Option<String>, 33 30 port: Option<u16>, ··· 95 92 96 93 Ok(Some(Box::new(player))) 97 94 } 95 + 96 + fn reconnect(&mut self) -> Result<(CastDevice, String), Error> { 97 + let cast_device = match CastDevice::connect_without_host_verification( 98 + self.host.clone().unwrap(), 99 + self.port.unwrap(), 100 + ) { 101 + Ok(cast_device) => cast_device, 102 + Err(err) => panic!("Could not establish connection with Cast Device: {:?}", err), 103 + }; 104 + 105 + cast_device 106 + .connection 107 + .connect(DEFAULT_DESTINATION_ID.to_string())?; 108 + cast_device.heartbeat.ping()?; 109 + 110 + let app_to_run = CastDeviceApp::from_str(DEFAULT_APP_ID).unwrap(); 111 + let app = cast_device.receiver.launch_app(&app_to_run)?; 112 + 113 + cast_device 114 + .connection 115 + .connect(app.transport_id.as_str()) 116 + .unwrap(); 117 + 118 + Ok((cast_device, app.transport_id)) 119 + } 120 + 121 + fn current_app_session(&mut self) -> Result<(CastDevice, String, i32, String), Error> { 122 + let app_to_manage = CastDeviceApp::from_str(DEFAULT_APP_ID).unwrap(); 123 + 124 + let (cast_device, _) = self.reconnect()?; 125 + 126 + cast_device 127 + .connection 128 + .connect(DEFAULT_DESTINATION_ID.to_string()) 129 + .unwrap(); 130 + cast_device.heartbeat.ping().unwrap(); 131 + 132 + let status = cast_device.receiver.get_status().unwrap(); 133 + 134 + let app = status 135 + .applications 136 + .iter() 137 + .find(|app| CastDeviceApp::from_str(app.app_id.as_str()).unwrap() == app_to_manage); 138 + 139 + match app { 140 + Some(app) => { 141 + cast_device 142 + .connection 143 + .connect(app.transport_id.as_str()) 144 + .unwrap(); 145 + 146 + let status = cast_device 147 + .media 148 + .get_status(app.transport_id.as_str(), None) 149 + .unwrap(); 150 + let status = status.entries.first().unwrap(); 151 + let media_session_id = status.media_session_id; 152 + let transport_id = app.transport_id.as_str(); 153 + Ok(( 154 + cast_device, 155 + transport_id.to_string(), 156 + media_session_id, 157 + "".to_string(), 158 + )) 159 + } 160 + None => Err(Error::msg(format!("{:?} is not running", app_to_manage))), 161 + } 162 + } 98 163 } 99 164 100 165 #[async_trait] 101 166 impl<'a> Player for Chromecast<'a> { 102 - async fn play(&self, url: &str) -> Result<(), Error> { 167 + async fn play(&self) -> Result<(), Error> { 103 168 self.cmd_tx 104 169 .as_ref() 105 170 .unwrap() ··· 155 220 Ok(()) 156 221 } 157 222 158 - async fn volume(&self, level: f32) -> Result<(), Error> { 223 + async fn volume(&self, _level: f32) -> Result<(), Error> { 159 224 todo!() 160 225 } 161 226 162 - async fn load_tracks(&self) -> Result<(), Error> { 163 - todo!() 227 + async fn load_tracks(&self, tracks: Vec<Track>, start_index: Option<i32>) -> Result<(), Error> { 228 + let media = tracks 229 + .iter() 230 + .map(|track| Media { 231 + content_id: track.uri.clone(), 232 + content_type: "".to_string(), 233 + stream_type: StreamType::None, 234 + metadata: Some(Metadata::MusicTrack(MusicTrackMediaMetadata { 235 + title: Some(track.title.clone()), 236 + artist: Some(track.artists.first().unwrap().name.clone()), 237 + album_name: Some(track.album.as_ref().unwrap().title.clone()), 238 + album_artist: Some(track.artists.first().unwrap().name.clone()), 239 + track_number: track.track_number, 240 + disc_number: Some(track.disc_number), 241 + images: match &track.album.as_ref().unwrap().cover { 242 + Some(cover) => vec![Image { 243 + url: cover.clone(), 244 + dimensions: None, 245 + }], 246 + None => vec![], 247 + }, 248 + release_date: None, 249 + composer: None, 250 + })), 251 + duration: None, 252 + }) 253 + .collect::<Vec<Media>>(); 254 + 255 + let transport_id = self.transport_id.as_ref().map(|id| id.as_str()).unwrap(); 256 + 257 + if let Some(cast_device) = &self.client { 258 + cast_device.media.queue_load( 259 + transport_id, 260 + media.clone(), 261 + Some(start_index.unwrap_or(0)), 262 + None, 263 + )?; 264 + println!("[chromecast] Tracks loaded"); 265 + println!("[chromecast] Playing track {:#?}", media[0]); 266 + return Ok(()); 267 + } 268 + 269 + return Err(Error::msg("device not connected")); 164 270 } 165 271 166 - async fn play_next(&self) -> Result<(), Error> { 167 - todo!() 272 + async fn play_next(&self, track: Track) -> Result<(), Error> { 273 + if let Some(cmd_tx) = &self.cmd_tx { 274 + cmd_tx.send(CastPlayerCommand::PlayNext(track)).unwrap(); 275 + return Ok(()); 276 + } 277 + Err(Error::msg("Cast device is not connected")) 168 278 } 169 279 170 - async fn load(&self) -> Result<(), Error> { 171 - todo!() 280 + async fn load(&mut self, track: Track) -> Result<(), Error> { 281 + let (cast_device, transport_id, _, session_id) = self.current_app_session()?; 282 + 283 + cast_device.media.load( 284 + transport_id.as_str(), 285 + session_id.as_str(), 286 + &Media { 287 + content_id: track.uri, 288 + content_type: "".to_string(), 289 + stream_type: StreamType::Buffered, 290 + duration: None, 291 + metadata: None, 292 + }, 293 + )?; 294 + 295 + Ok(()) 172 296 } 173 297 174 - async fn get_current_playback(&self) -> Result<(), Error> { 175 - todo!() 298 + async fn get_current_playback(&mut self) -> Result<Playback, Error> { 299 + if self.host.is_none() || self.port.is_none() { 300 + return Err(Error::msg("No device connected")); 301 + } 302 + 303 + if self.client.is_none() { 304 + return Err(Error::msg("Cast device is not connected")); 305 + } 306 + 307 + let current_playback = self.current_playback.lock().unwrap(); 308 + match &current_playback.current { 309 + Some(playback) => return Ok(playback.clone()), 310 + None => return Ok(Playback::default()), 311 + } 176 312 } 177 313 178 - async fn get_current_tracklist(&self) -> Result<(), Error> { 314 + async fn get_current_tracklist(&self) -> Result<(Vec<Track>, Vec<Track>), Error> { 179 315 todo!() 180 316 } 181 317 182 - async fn play_track_at(&self) -> Result<(), Error> { 318 + async fn play_track_at(&self, _position: u32) -> Result<(), Error> { 183 319 todo!() 184 320 } 185 321 186 - async fn remove_track_at(&self) -> Result<(), Error> { 322 + async fn remove_track_at(&self, _position: u32) -> Result<(), Error> { 187 323 todo!() 188 324 } 189 325 ··· 518 654 Ok(()) 519 655 } 520 656 521 - fn handle_seek(&self, seconds: i32) -> Result<(), Error> { 657 + fn handle_seek(&self, _seconds: i32) -> Result<(), Error> { 522 658 todo!(); 523 659 } 524 660 525 661 fn handle_play_next(&self, track: Track) -> Result<(), Error> { 526 - todo!(); 662 + let items = vec![Media { 663 + content_id: track.uri.clone(), 664 + content_type: "".to_string(), 665 + stream_type: StreamType::Buffered, 666 + metadata: Some(Metadata::MusicTrack(MusicTrackMediaMetadata { 667 + title: Some(track.title.clone()), 668 + artist: Some(track.artists.first().unwrap().name.clone()), 669 + album_name: Some(track.album.as_ref().unwrap().title.clone()), 670 + album_artist: Some(track.artists.first().unwrap().name.clone()), 671 + track_number: track.track_number, 672 + disc_number: Some(track.disc_number), 673 + images: match &track.album.as_ref().unwrap().cover { 674 + Some(cover) => vec![Image { 675 + url: cover.clone(), 676 + dimensions: None, 677 + }], 678 + None => vec![], 679 + }, 680 + release_date: None, 681 + composer: None, 682 + })), 683 + duration: None, 684 + }]; 685 + let playback = self.get_current_playback()?; 686 + 687 + let tracklist = playback.items; 688 + let mut tracklist = tracklist.iter(); 689 + let mut before: Option<i32> = None; 690 + loop { 691 + let cursor = tracklist.next(); 692 + if cursor.is_none() { 693 + break; 694 + } 695 + let (_, item_id) = cursor.unwrap(); 696 + if *item_id == playback.current_item_id.unwrap() { 697 + let cursor = tracklist.next(); 698 + before = cursor.map(|(_, item_id)| *item_id); 699 + break; 700 + } 701 + } 702 + 703 + let (transport_id, media_session_id, _) = self.current_app_session()?; 704 + self.cast_device.media.queue_insert( 705 + transport_id.as_str(), 706 + media_session_id, 707 + items, 708 + before, 709 + )?; 710 + return Ok(()); 527 711 } 528 712 529 713 fn handle_disconnect(&self) -> Result<(), Error> {
+1 -1
crates/chromecast/src/main.rs
··· 2 2 use rockbox_types::device::Device; 3 3 4 4 pub fn main() { 5 - let player = Chromecast::connect(Device { 5 + let _player = Chromecast::connect(Device { 6 6 host: "192.168.1.60".into(), 7 7 port: 8009, 8 8 ..Default::default()
crates/chromecast/src/tracklist.rs

This is a binary file and will not be displayed.

crates/chromecast/src/types/mod.rs crates/traits/src/types/mod.rs
crates/chromecast/src/types/playback.rs crates/traits/src/types/playback.rs
crates/chromecast/src/types/track.rs crates/traits/src/types/track.rs
+6
crates/discovery/Cargo.toml
··· 4 4 version = "0.1.0" 5 5 6 6 [dependencies] 7 + async-stream = "0.3.6" 8 + futures-util = "0.3.31" 9 + libmdns = "0.9.1" 10 + mdns = "3.0.0" 11 + mdns-sd = "0.5.9" 12 + tokio = {version = "1.36.0", features = ["full"]}
+87 -9
crates/discovery/src/lib.rs
··· 1 - pub fn add(left: u64, right: u64) -> u64 { 2 - left + right 1 + use async_stream::stream; 2 + use futures_util::Stream; 3 + use mdns_sd::{ServiceDaemon, ServiceEvent, ServiceInfo}; 4 + use std::{env, thread}; 5 + 6 + pub const ROCKBOX_SERVICE_NAME: &'static str = "_rockbox._tcp.local."; 7 + pub const MUSIC_PLAYER_SERVICE_NAME: &'static str = "_music-player._tcp.local."; 8 + pub const XBMC_SERVICE_NAME: &'static str = "_xbmc-jsonrpc-h._tcp.local."; 9 + pub const CHROMECAST_SERVICE_NAME: &'static str = "_googlecast._tcp.local."; 10 + 11 + pub struct MdnsResponder { 12 + responder: libmdns::Responder, 13 + svc: Vec<libmdns::Service>, 3 14 } 4 15 5 - #[cfg(test)] 6 - mod tests { 7 - use super::*; 16 + impl MdnsResponder { 17 + pub fn new() -> Self { 18 + let responder = libmdns::Responder::new().unwrap(); 19 + Self { 20 + responder, 21 + svc: vec![], 22 + } 23 + } 8 24 9 - #[test] 10 - fn it_works() { 11 - let result = add(2, 2); 12 - assert_eq!(result, 4); 25 + pub fn register_service(&mut self, name: &str, port: u16) { 26 + let device_name = "rockbox"; 27 + let device_name = format!("device_name={}", device_name); 28 + 29 + self.svc.push(self.responder.register( 30 + "_rockbox._tcp".to_owned(), 31 + name.to_owned(), 32 + port, 33 + &["path=/", device_name.as_str()], 34 + )); 35 + } 36 + } 37 + 38 + pub fn register_services() { 39 + let device_id = "123"; 40 + let http_service = format!("http-{}", device_id); 41 + let graphql_service = format!("graphql-{}", device_id); 42 + let grpc_service = format!("grpc-{}", device_id); 43 + let mpd_service = format!("mpd-{}", device_id); 44 + 45 + thread::spawn(move || { 46 + let http_port = env::var("ROCKBOX_HTTP_PORT").unwrap_or("6061".to_string()); 47 + let graphql_port = env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 48 + let grpc_port = env::var("ROCKBOX_PORT").unwrap_or("6063".to_string()); 49 + let mpd_port = env::var("ROCKBOX_MPD_PORT").unwrap_or("6600".to_string()); 50 + let mut responder = MdnsResponder::new(); 51 + responder.register_service(&http_service, http_port.parse::<u16>().unwrap()); 52 + responder.register_service(&graphql_service, graphql_port.parse::<u16>().unwrap()); 53 + responder.register_service(&grpc_service, grpc_port.parse::<u16>().unwrap()); 54 + responder.register_service(&mpd_service, mpd_port.parse::<u16>().unwrap()); 55 + loop { 56 + ::std::thread::sleep(::std::time::Duration::from_secs(10)); 57 + } 58 + }); 59 + } 60 + 61 + pub fn register(name: &str, port: u16) { 62 + let device_name = env::var("ROCKBOX_DEVICE_NAME").unwrap_or("rockbox".to_string()); 63 + let device_name = format!("device_name={}", device_name); 64 + 65 + let responder = libmdns::Responder::new().unwrap(); 66 + let _svc = responder.register( 67 + "_rockbox._tcp".to_owned(), 68 + name.to_owned(), 69 + port, 70 + &["path=/", device_name.as_str()], 71 + ); 72 + 73 + loop { 74 + ::std::thread::sleep(::std::time::Duration::from_secs(10)); 75 + } 76 + } 77 + 78 + pub fn discover(service_name: &str) -> impl Stream<Item = ServiceInfo> { 79 + let mdns = ServiceDaemon::new().unwrap(); 80 + let receiver = mdns.browse(&service_name).expect("Failed to browse"); 81 + 82 + stream! { 83 + while let Ok(event) = receiver.recv() { 84 + match event { 85 + ServiceEvent::ServiceResolved(info) => { 86 + yield info; 87 + } 88 + _ => {} 89 + } 90 + } 13 91 } 14 92 }
+17
crates/discovery/src/main.rs
··· 1 + use futures_util::StreamExt; 2 + use mdns::Error; 3 + use rockbox_discovery::{discover, MdnsResponder, ROCKBOX_SERVICE_NAME}; 4 + 5 + #[tokio::main] 6 + async fn main() -> Result<(), Error> { 7 + let mut responder = MdnsResponder::new(); 8 + responder.register_service("service1", 8080); 9 + responder.register_service("service2", 8080); 10 + responder.register_service("service3", 8080); 11 + let services = discover(ROCKBOX_SERVICE_NAME); 12 + tokio::pin!(services); 13 + while let Some(srv) = services.next().await { 14 + println!("got = {:?}", srv); 15 + } 16 + Ok(()) 17 + }
+47
crates/graphql/src/schema/device.rs
··· 1 + use async_graphql::*; 2 + 3 + use crate::rockbox_url; 4 + 5 + use super::objects::device::Device; 6 + 7 + #[derive(Default)] 8 + pub struct DeviceQuery; 9 + 10 + #[Object] 11 + impl DeviceQuery { 12 + async fn devices(&self, _ctx: &Context<'_>) -> Result<Vec<Device>, Error> { 13 + let client = reqwest::Client::new(); 14 + let url = format!("{}/devices", rockbox_url()); 15 + let response = client.get(&url).send().await?; 16 + let response = response.json::<Vec<Device>>().await?; 17 + Ok(response) 18 + } 19 + 20 + async fn device(&self, _ctx: &Context<'_>, id: String) -> Result<Option<Device>, Error> { 21 + let client = reqwest::Client::new(); 22 + let url = format!("{}/devices/{}", rockbox_url(), id); 23 + let response = client.get(&url).send().await?; 24 + let response = response.json::<Option<Device>>().await?; 25 + Ok(response) 26 + } 27 + } 28 + 29 + #[derive(Default)] 30 + pub struct DeviceMutation; 31 + 32 + #[Object] 33 + impl DeviceMutation { 34 + async fn connect(&self, _ctx: &Context<'_>, id: String) -> Result<bool, Error> { 35 + let client = reqwest::Client::new(); 36 + let url = format!("{}/devices/{}/connect", rockbox_url(), id); 37 + client.put(&url).send().await?; 38 + Ok(true) 39 + } 40 + 41 + async fn disconnect(&self, _ctx: &Context<'_>, id: String) -> Result<bool, Error> { 42 + let client = reqwest::Client::new(); 43 + let url = format!("{}/devices/{}/disconnect", rockbox_url(), id); 44 + client.put(&url).send().await?; 45 + Ok(true) 46 + } 47 + }
+4
crates/graphql/src/schema/mod.rs
··· 1 1 use async_graphql::{MergedObject, MergedSubscription}; 2 2 use browse::BrowseQuery; 3 + use device::{DeviceMutation, DeviceQuery}; 3 4 use library::{LibraryMutation, LibraryQuery}; 4 5 use playback::{PlaybackMutation, PlaybackQuery, PlaybackSubscription}; 5 6 use playlist::{PlaylistMutation, PlaylistQuery, PlaylistSubscription}; ··· 8 9 use system::SystemQuery; 9 10 10 11 pub mod browse; 12 + pub mod device; 11 13 pub mod library; 12 14 pub mod metadata; 13 15 pub mod objects; ··· 20 22 #[derive(MergedObject, Default)] 21 23 pub struct Query( 22 24 BrowseQuery, 25 + DeviceQuery, 23 26 LibraryQuery, 24 27 PlaybackQuery, 25 28 PlaylistQuery, ··· 30 33 31 34 #[derive(MergedObject, Default)] 32 35 pub struct Mutation( 36 + DeviceMutation, 33 37 PlaybackMutation, 34 38 PlaylistMutation, 35 39 SoundMutation,
+69
crates/graphql/src/schema/objects/device.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Default, Clone, Serialize, Deserialize)] 5 + pub struct Device { 6 + pub id: String, 7 + pub name: String, 8 + pub host: String, 9 + pub ip: String, 10 + pub port: u16, 11 + pub service: String, 12 + pub app: String, 13 + pub is_connected: bool, 14 + pub base_url: Option<String>, 15 + pub is_cast_device: bool, 16 + pub is_source_device: bool, 17 + pub is_current_device: bool, 18 + } 19 + 20 + #[Object] 21 + impl Device { 22 + async fn id(&self) -> &str { 23 + &self.id 24 + } 25 + 26 + async fn name(&self) -> &str { 27 + &self.name 28 + } 29 + 30 + async fn host(&self) -> &str { 31 + &self.host 32 + } 33 + 34 + async fn ip(&self) -> &str { 35 + &self.ip 36 + } 37 + 38 + async fn port(&self) -> i32 { 39 + self.port as i32 40 + } 41 + 42 + async fn service(&self) -> &str { 43 + &self.service 44 + } 45 + 46 + async fn app(&self) -> &str { 47 + &self.app 48 + } 49 + 50 + async fn is_connected(&self) -> bool { 51 + self.is_connected 52 + } 53 + 54 + async fn base_url(&self) -> Option<&str> { 55 + self.base_url.as_deref() 56 + } 57 + 58 + async fn is_cast_device(&self) -> bool { 59 + self.is_cast_device 60 + } 61 + 62 + async fn is_source_device(&self) -> bool { 63 + self.is_source_device 64 + } 65 + 66 + async fn is_current_device(&self) -> bool { 67 + self.is_current_device 68 + } 69 + }
+1
crates/graphql/src/schema/objects/mod.rs
··· 2 2 pub mod artist; 3 3 pub mod audio_status; 4 4 pub mod compressor_settings; 5 + pub mod device; 5 6 pub mod entry; 6 7 pub mod eq_band_setting; 7 8 pub mod new_global_settings;
+1
crates/rpc/build.rs
··· 5 5 .compile( 6 6 &[ 7 7 "proto/rockbox/v1alpha1/browse.proto", 8 + "proto/rockbox/v1alpha1/device.proto", 8 9 "proto/rockbox/v1alpha1/library.proto", 9 10 "proto/rockbox/v1alpha1/metadata.proto", 10 11 "proto/rockbox/v1alpha1/playback.proto",
+55
crates/rpc/proto/rockbox/v1alpha1/device.proto
··· 1 + syntax = "proto3"; 2 + 3 + package rockbox.v1alpha1; 4 + 5 + message GetDevicesRequest {} 6 + 7 + message GetDevicesResponse { 8 + repeated Device devices = 1; 9 + } 10 + 11 + message GetDeviceRequest { 12 + string id = 1; 13 + } 14 + 15 + message GetDeviceResponse { 16 + Device device = 1; 17 + } 18 + 19 + message ConnectDeviceRequest { 20 + string id = 1; 21 + } 22 + 23 + message ConnectDeviceResponse { 24 + Device device = 1; 25 + } 26 + 27 + message DisconnectDeviceRequest { 28 + string id = 1; 29 + } 30 + 31 + message DisconnectDeviceResponse { 32 + Device device = 1; 33 + } 34 + 35 + message Device { 36 + string id = 1; 37 + string name = 2; 38 + string host = 3; 39 + string ip = 4; 40 + uint32 port = 5; 41 + string service = 6; 42 + string app = 7; 43 + bool is_connected = 8; 44 + optional string base_url = 9; 45 + bool is_cast_device = 10; 46 + bool is_source_device = 11; 47 + bool is_current_device = 12; 48 + } 49 + 50 + service DeviceService { 51 + rpc GetDevices(GetDevicesRequest) returns (GetDevicesResponse); 52 + rpc GetDevice(GetDeviceRequest) returns (GetDeviceResponse); 53 + rpc ConnectDevice(ConnectDeviceRequest) returns (ConnectDeviceResponse); 54 + rpc DisconnectDevice(DisconnectDeviceRequest) returns (DisconnectDeviceResponse); 55 + }
+584
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 323 323 const NAME: &'static str = SERVICE_NAME; 324 324 } 325 325 } 326 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 327 + pub struct GetDevicesRequest {} 328 + #[derive(Clone, PartialEq, ::prost::Message)] 329 + pub struct GetDevicesResponse { 330 + #[prost(message, repeated, tag = "1")] 331 + pub devices: ::prost::alloc::vec::Vec<Device>, 332 + } 333 + #[derive(Clone, PartialEq, ::prost::Message)] 334 + pub struct GetDeviceRequest { 335 + #[prost(string, tag = "1")] 336 + pub id: ::prost::alloc::string::String, 337 + } 338 + #[derive(Clone, PartialEq, ::prost::Message)] 339 + pub struct GetDeviceResponse { 340 + #[prost(message, optional, tag = "1")] 341 + pub device: ::core::option::Option<Device>, 342 + } 343 + #[derive(Clone, PartialEq, ::prost::Message)] 344 + pub struct ConnectDeviceRequest { 345 + #[prost(string, tag = "1")] 346 + pub id: ::prost::alloc::string::String, 347 + } 348 + #[derive(Clone, PartialEq, ::prost::Message)] 349 + pub struct ConnectDeviceResponse { 350 + #[prost(message, optional, tag = "1")] 351 + pub device: ::core::option::Option<Device>, 352 + } 353 + #[derive(Clone, PartialEq, ::prost::Message)] 354 + pub struct DisconnectDeviceRequest { 355 + #[prost(string, tag = "1")] 356 + pub id: ::prost::alloc::string::String, 357 + } 358 + #[derive(Clone, PartialEq, ::prost::Message)] 359 + pub struct DisconnectDeviceResponse { 360 + #[prost(message, optional, tag = "1")] 361 + pub device: ::core::option::Option<Device>, 362 + } 363 + #[derive(Clone, PartialEq, ::prost::Message)] 364 + pub struct Device { 365 + #[prost(string, tag = "1")] 366 + pub id: ::prost::alloc::string::String, 367 + #[prost(string, tag = "2")] 368 + pub name: ::prost::alloc::string::String, 369 + #[prost(string, tag = "3")] 370 + pub host: ::prost::alloc::string::String, 371 + #[prost(string, tag = "4")] 372 + pub ip: ::prost::alloc::string::String, 373 + #[prost(uint32, tag = "5")] 374 + pub port: u32, 375 + #[prost(string, tag = "6")] 376 + pub service: ::prost::alloc::string::String, 377 + #[prost(string, tag = "7")] 378 + pub app: ::prost::alloc::string::String, 379 + #[prost(bool, tag = "8")] 380 + pub is_connected: bool, 381 + #[prost(string, optional, tag = "9")] 382 + pub base_url: ::core::option::Option<::prost::alloc::string::String>, 383 + #[prost(bool, tag = "10")] 384 + pub is_cast_device: bool, 385 + #[prost(bool, tag = "11")] 386 + pub is_source_device: bool, 387 + #[prost(bool, tag = "12")] 388 + pub is_current_device: bool, 389 + } 390 + /// Generated client implementations. 391 + pub mod device_service_client { 392 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 393 + use tonic::codegen::*; 394 + use tonic::codegen::http::Uri; 395 + #[derive(Debug, Clone)] 396 + pub struct DeviceServiceClient<T> { 397 + inner: tonic::client::Grpc<T>, 398 + } 399 + impl DeviceServiceClient<tonic::transport::Channel> { 400 + /// Attempt to create a new client by connecting to a given endpoint. 401 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 402 + where 403 + D: TryInto<tonic::transport::Endpoint>, 404 + D::Error: Into<StdError>, 405 + { 406 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 407 + Ok(Self::new(conn)) 408 + } 409 + } 410 + impl<T> DeviceServiceClient<T> 411 + where 412 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 413 + T::Error: Into<StdError>, 414 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 415 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 416 + { 417 + pub fn new(inner: T) -> Self { 418 + let inner = tonic::client::Grpc::new(inner); 419 + Self { inner } 420 + } 421 + pub fn with_origin(inner: T, origin: Uri) -> Self { 422 + let inner = tonic::client::Grpc::with_origin(inner, origin); 423 + Self { inner } 424 + } 425 + pub fn with_interceptor<F>( 426 + inner: T, 427 + interceptor: F, 428 + ) -> DeviceServiceClient<InterceptedService<T, F>> 429 + where 430 + F: tonic::service::Interceptor, 431 + T::ResponseBody: Default, 432 + T: tonic::codegen::Service< 433 + http::Request<tonic::body::BoxBody>, 434 + Response = http::Response< 435 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 436 + >, 437 + >, 438 + <T as tonic::codegen::Service< 439 + http::Request<tonic::body::BoxBody>, 440 + >>::Error: Into<StdError> + std::marker::Send + std::marker::Sync, 441 + { 442 + DeviceServiceClient::new(InterceptedService::new(inner, interceptor)) 443 + } 444 + /// Compress requests with the given encoding. 445 + /// 446 + /// This requires the server to support it otherwise it might respond with an 447 + /// error. 448 + #[must_use] 449 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 450 + self.inner = self.inner.send_compressed(encoding); 451 + self 452 + } 453 + /// Enable decompressing responses. 454 + #[must_use] 455 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 456 + self.inner = self.inner.accept_compressed(encoding); 457 + self 458 + } 459 + /// Limits the maximum size of a decoded message. 460 + /// 461 + /// Default: `4MB` 462 + #[must_use] 463 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 464 + self.inner = self.inner.max_decoding_message_size(limit); 465 + self 466 + } 467 + /// Limits the maximum size of an encoded message. 468 + /// 469 + /// Default: `usize::MAX` 470 + #[must_use] 471 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 472 + self.inner = self.inner.max_encoding_message_size(limit); 473 + self 474 + } 475 + pub async fn get_devices( 476 + &mut self, 477 + request: impl tonic::IntoRequest<super::GetDevicesRequest>, 478 + ) -> std::result::Result< 479 + tonic::Response<super::GetDevicesResponse>, 480 + tonic::Status, 481 + > { 482 + self.inner 483 + .ready() 484 + .await 485 + .map_err(|e| { 486 + tonic::Status::new( 487 + tonic::Code::Unknown, 488 + format!("Service was not ready: {}", e.into()), 489 + ) 490 + })?; 491 + let codec = tonic::codec::ProstCodec::default(); 492 + let path = http::uri::PathAndQuery::from_static( 493 + "/rockbox.v1alpha1.DeviceService/GetDevices", 494 + ); 495 + let mut req = request.into_request(); 496 + req.extensions_mut() 497 + .insert(GrpcMethod::new("rockbox.v1alpha1.DeviceService", "GetDevices")); 498 + self.inner.unary(req, path, codec).await 499 + } 500 + pub async fn get_device( 501 + &mut self, 502 + request: impl tonic::IntoRequest<super::GetDeviceRequest>, 503 + ) -> std::result::Result< 504 + tonic::Response<super::GetDeviceResponse>, 505 + tonic::Status, 506 + > { 507 + self.inner 508 + .ready() 509 + .await 510 + .map_err(|e| { 511 + tonic::Status::new( 512 + tonic::Code::Unknown, 513 + format!("Service was not ready: {}", e.into()), 514 + ) 515 + })?; 516 + let codec = tonic::codec::ProstCodec::default(); 517 + let path = http::uri::PathAndQuery::from_static( 518 + "/rockbox.v1alpha1.DeviceService/GetDevice", 519 + ); 520 + let mut req = request.into_request(); 521 + req.extensions_mut() 522 + .insert(GrpcMethod::new("rockbox.v1alpha1.DeviceService", "GetDevice")); 523 + self.inner.unary(req, path, codec).await 524 + } 525 + pub async fn connect_device( 526 + &mut self, 527 + request: impl tonic::IntoRequest<super::ConnectDeviceRequest>, 528 + ) -> std::result::Result< 529 + tonic::Response<super::ConnectDeviceResponse>, 530 + tonic::Status, 531 + > { 532 + self.inner 533 + .ready() 534 + .await 535 + .map_err(|e| { 536 + tonic::Status::new( 537 + tonic::Code::Unknown, 538 + format!("Service was not ready: {}", e.into()), 539 + ) 540 + })?; 541 + let codec = tonic::codec::ProstCodec::default(); 542 + let path = http::uri::PathAndQuery::from_static( 543 + "/rockbox.v1alpha1.DeviceService/ConnectDevice", 544 + ); 545 + let mut req = request.into_request(); 546 + req.extensions_mut() 547 + .insert( 548 + GrpcMethod::new("rockbox.v1alpha1.DeviceService", "ConnectDevice"), 549 + ); 550 + self.inner.unary(req, path, codec).await 551 + } 552 + pub async fn disconnect_device( 553 + &mut self, 554 + request: impl tonic::IntoRequest<super::DisconnectDeviceRequest>, 555 + ) -> std::result::Result< 556 + tonic::Response<super::DisconnectDeviceResponse>, 557 + tonic::Status, 558 + > { 559 + self.inner 560 + .ready() 561 + .await 562 + .map_err(|e| { 563 + tonic::Status::new( 564 + tonic::Code::Unknown, 565 + format!("Service was not ready: {}", e.into()), 566 + ) 567 + })?; 568 + let codec = tonic::codec::ProstCodec::default(); 569 + let path = http::uri::PathAndQuery::from_static( 570 + "/rockbox.v1alpha1.DeviceService/DisconnectDevice", 571 + ); 572 + let mut req = request.into_request(); 573 + req.extensions_mut() 574 + .insert( 575 + GrpcMethod::new("rockbox.v1alpha1.DeviceService", "DisconnectDevice"), 576 + ); 577 + self.inner.unary(req, path, codec).await 578 + } 579 + } 580 + } 581 + /// Generated server implementations. 582 + pub mod device_service_server { 583 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 584 + use tonic::codegen::*; 585 + /// Generated trait containing gRPC methods that should be implemented for use with DeviceServiceServer. 586 + #[async_trait] 587 + pub trait DeviceService: std::marker::Send + std::marker::Sync + 'static { 588 + async fn get_devices( 589 + &self, 590 + request: tonic::Request<super::GetDevicesRequest>, 591 + ) -> std::result::Result< 592 + tonic::Response<super::GetDevicesResponse>, 593 + tonic::Status, 594 + >; 595 + async fn get_device( 596 + &self, 597 + request: tonic::Request<super::GetDeviceRequest>, 598 + ) -> std::result::Result< 599 + tonic::Response<super::GetDeviceResponse>, 600 + tonic::Status, 601 + >; 602 + async fn connect_device( 603 + &self, 604 + request: tonic::Request<super::ConnectDeviceRequest>, 605 + ) -> std::result::Result< 606 + tonic::Response<super::ConnectDeviceResponse>, 607 + tonic::Status, 608 + >; 609 + async fn disconnect_device( 610 + &self, 611 + request: tonic::Request<super::DisconnectDeviceRequest>, 612 + ) -> std::result::Result< 613 + tonic::Response<super::DisconnectDeviceResponse>, 614 + tonic::Status, 615 + >; 616 + } 617 + #[derive(Debug)] 618 + pub struct DeviceServiceServer<T> { 619 + inner: Arc<T>, 620 + accept_compression_encodings: EnabledCompressionEncodings, 621 + send_compression_encodings: EnabledCompressionEncodings, 622 + max_decoding_message_size: Option<usize>, 623 + max_encoding_message_size: Option<usize>, 624 + } 625 + impl<T> DeviceServiceServer<T> { 626 + pub fn new(inner: T) -> Self { 627 + Self::from_arc(Arc::new(inner)) 628 + } 629 + pub fn from_arc(inner: Arc<T>) -> Self { 630 + Self { 631 + inner, 632 + accept_compression_encodings: Default::default(), 633 + send_compression_encodings: Default::default(), 634 + max_decoding_message_size: None, 635 + max_encoding_message_size: None, 636 + } 637 + } 638 + pub fn with_interceptor<F>( 639 + inner: T, 640 + interceptor: F, 641 + ) -> InterceptedService<Self, F> 642 + where 643 + F: tonic::service::Interceptor, 644 + { 645 + InterceptedService::new(Self::new(inner), interceptor) 646 + } 647 + /// Enable decompressing requests with the given encoding. 648 + #[must_use] 649 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 650 + self.accept_compression_encodings.enable(encoding); 651 + self 652 + } 653 + /// Compress responses with the given encoding, if the client supports it. 654 + #[must_use] 655 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 656 + self.send_compression_encodings.enable(encoding); 657 + self 658 + } 659 + /// Limits the maximum size of a decoded message. 660 + /// 661 + /// Default: `4MB` 662 + #[must_use] 663 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 664 + self.max_decoding_message_size = Some(limit); 665 + self 666 + } 667 + /// Limits the maximum size of an encoded message. 668 + /// 669 + /// Default: `usize::MAX` 670 + #[must_use] 671 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 672 + self.max_encoding_message_size = Some(limit); 673 + self 674 + } 675 + } 676 + impl<T, B> tonic::codegen::Service<http::Request<B>> for DeviceServiceServer<T> 677 + where 678 + T: DeviceService, 679 + B: Body + std::marker::Send + 'static, 680 + B::Error: Into<StdError> + std::marker::Send + 'static, 681 + { 682 + type Response = http::Response<tonic::body::BoxBody>; 683 + type Error = std::convert::Infallible; 684 + type Future = BoxFuture<Self::Response, Self::Error>; 685 + fn poll_ready( 686 + &mut self, 687 + _cx: &mut Context<'_>, 688 + ) -> Poll<std::result::Result<(), Self::Error>> { 689 + Poll::Ready(Ok(())) 690 + } 691 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 692 + match req.uri().path() { 693 + "/rockbox.v1alpha1.DeviceService/GetDevices" => { 694 + #[allow(non_camel_case_types)] 695 + struct GetDevicesSvc<T: DeviceService>(pub Arc<T>); 696 + impl< 697 + T: DeviceService, 698 + > tonic::server::UnaryService<super::GetDevicesRequest> 699 + for GetDevicesSvc<T> { 700 + type Response = super::GetDevicesResponse; 701 + type Future = BoxFuture< 702 + tonic::Response<Self::Response>, 703 + tonic::Status, 704 + >; 705 + fn call( 706 + &mut self, 707 + request: tonic::Request<super::GetDevicesRequest>, 708 + ) -> Self::Future { 709 + let inner = Arc::clone(&self.0); 710 + let fut = async move { 711 + <T as DeviceService>::get_devices(&inner, request).await 712 + }; 713 + Box::pin(fut) 714 + } 715 + } 716 + let accept_compression_encodings = self.accept_compression_encodings; 717 + let send_compression_encodings = self.send_compression_encodings; 718 + let max_decoding_message_size = self.max_decoding_message_size; 719 + let max_encoding_message_size = self.max_encoding_message_size; 720 + let inner = self.inner.clone(); 721 + let fut = async move { 722 + let method = GetDevicesSvc(inner); 723 + let codec = tonic::codec::ProstCodec::default(); 724 + let mut grpc = tonic::server::Grpc::new(codec) 725 + .apply_compression_config( 726 + accept_compression_encodings, 727 + send_compression_encodings, 728 + ) 729 + .apply_max_message_size_config( 730 + max_decoding_message_size, 731 + max_encoding_message_size, 732 + ); 733 + let res = grpc.unary(method, req).await; 734 + Ok(res) 735 + }; 736 + Box::pin(fut) 737 + } 738 + "/rockbox.v1alpha1.DeviceService/GetDevice" => { 739 + #[allow(non_camel_case_types)] 740 + struct GetDeviceSvc<T: DeviceService>(pub Arc<T>); 741 + impl< 742 + T: DeviceService, 743 + > tonic::server::UnaryService<super::GetDeviceRequest> 744 + for GetDeviceSvc<T> { 745 + type Response = super::GetDeviceResponse; 746 + type Future = BoxFuture< 747 + tonic::Response<Self::Response>, 748 + tonic::Status, 749 + >; 750 + fn call( 751 + &mut self, 752 + request: tonic::Request<super::GetDeviceRequest>, 753 + ) -> Self::Future { 754 + let inner = Arc::clone(&self.0); 755 + let fut = async move { 756 + <T as DeviceService>::get_device(&inner, request).await 757 + }; 758 + Box::pin(fut) 759 + } 760 + } 761 + let accept_compression_encodings = self.accept_compression_encodings; 762 + let send_compression_encodings = self.send_compression_encodings; 763 + let max_decoding_message_size = self.max_decoding_message_size; 764 + let max_encoding_message_size = self.max_encoding_message_size; 765 + let inner = self.inner.clone(); 766 + let fut = async move { 767 + let method = GetDeviceSvc(inner); 768 + let codec = tonic::codec::ProstCodec::default(); 769 + let mut grpc = tonic::server::Grpc::new(codec) 770 + .apply_compression_config( 771 + accept_compression_encodings, 772 + send_compression_encodings, 773 + ) 774 + .apply_max_message_size_config( 775 + max_decoding_message_size, 776 + max_encoding_message_size, 777 + ); 778 + let res = grpc.unary(method, req).await; 779 + Ok(res) 780 + }; 781 + Box::pin(fut) 782 + } 783 + "/rockbox.v1alpha1.DeviceService/ConnectDevice" => { 784 + #[allow(non_camel_case_types)] 785 + struct ConnectDeviceSvc<T: DeviceService>(pub Arc<T>); 786 + impl< 787 + T: DeviceService, 788 + > tonic::server::UnaryService<super::ConnectDeviceRequest> 789 + for ConnectDeviceSvc<T> { 790 + type Response = super::ConnectDeviceResponse; 791 + type Future = BoxFuture< 792 + tonic::Response<Self::Response>, 793 + tonic::Status, 794 + >; 795 + fn call( 796 + &mut self, 797 + request: tonic::Request<super::ConnectDeviceRequest>, 798 + ) -> Self::Future { 799 + let inner = Arc::clone(&self.0); 800 + let fut = async move { 801 + <T as DeviceService>::connect_device(&inner, request).await 802 + }; 803 + Box::pin(fut) 804 + } 805 + } 806 + let accept_compression_encodings = self.accept_compression_encodings; 807 + let send_compression_encodings = self.send_compression_encodings; 808 + let max_decoding_message_size = self.max_decoding_message_size; 809 + let max_encoding_message_size = self.max_encoding_message_size; 810 + let inner = self.inner.clone(); 811 + let fut = async move { 812 + let method = ConnectDeviceSvc(inner); 813 + let codec = tonic::codec::ProstCodec::default(); 814 + let mut grpc = tonic::server::Grpc::new(codec) 815 + .apply_compression_config( 816 + accept_compression_encodings, 817 + send_compression_encodings, 818 + ) 819 + .apply_max_message_size_config( 820 + max_decoding_message_size, 821 + max_encoding_message_size, 822 + ); 823 + let res = grpc.unary(method, req).await; 824 + Ok(res) 825 + }; 826 + Box::pin(fut) 827 + } 828 + "/rockbox.v1alpha1.DeviceService/DisconnectDevice" => { 829 + #[allow(non_camel_case_types)] 830 + struct DisconnectDeviceSvc<T: DeviceService>(pub Arc<T>); 831 + impl< 832 + T: DeviceService, 833 + > tonic::server::UnaryService<super::DisconnectDeviceRequest> 834 + for DisconnectDeviceSvc<T> { 835 + type Response = super::DisconnectDeviceResponse; 836 + type Future = BoxFuture< 837 + tonic::Response<Self::Response>, 838 + tonic::Status, 839 + >; 840 + fn call( 841 + &mut self, 842 + request: tonic::Request<super::DisconnectDeviceRequest>, 843 + ) -> Self::Future { 844 + let inner = Arc::clone(&self.0); 845 + let fut = async move { 846 + <T as DeviceService>::disconnect_device(&inner, request) 847 + .await 848 + }; 849 + Box::pin(fut) 850 + } 851 + } 852 + let accept_compression_encodings = self.accept_compression_encodings; 853 + let send_compression_encodings = self.send_compression_encodings; 854 + let max_decoding_message_size = self.max_decoding_message_size; 855 + let max_encoding_message_size = self.max_encoding_message_size; 856 + let inner = self.inner.clone(); 857 + let fut = async move { 858 + let method = DisconnectDeviceSvc(inner); 859 + let codec = tonic::codec::ProstCodec::default(); 860 + let mut grpc = tonic::server::Grpc::new(codec) 861 + .apply_compression_config( 862 + accept_compression_encodings, 863 + send_compression_encodings, 864 + ) 865 + .apply_max_message_size_config( 866 + max_decoding_message_size, 867 + max_encoding_message_size, 868 + ); 869 + let res = grpc.unary(method, req).await; 870 + Ok(res) 871 + }; 872 + Box::pin(fut) 873 + } 874 + _ => { 875 + Box::pin(async move { 876 + Ok( 877 + http::Response::builder() 878 + .status(200) 879 + .header("grpc-status", tonic::Code::Unimplemented as i32) 880 + .header( 881 + http::header::CONTENT_TYPE, 882 + tonic::metadata::GRPC_CONTENT_TYPE, 883 + ) 884 + .body(empty_body()) 885 + .unwrap(), 886 + ) 887 + }) 888 + } 889 + } 890 + } 891 + } 892 + impl<T> Clone for DeviceServiceServer<T> { 893 + fn clone(&self) -> Self { 894 + let inner = self.inner.clone(); 895 + Self { 896 + inner, 897 + accept_compression_encodings: self.accept_compression_encodings, 898 + send_compression_encodings: self.send_compression_encodings, 899 + max_decoding_message_size: self.max_decoding_message_size, 900 + max_encoding_message_size: self.max_encoding_message_size, 901 + } 902 + } 903 + } 904 + /// Generated gRPC service name 905 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.DeviceService"; 906 + impl<T> tonic::server::NamedService for DeviceServiceServer<T> { 907 + const NAME: &'static str = SERVICE_NAME; 908 + } 909 + } 326 910 #[derive(Clone, PartialEq, ::prost::Message)] 327 911 pub struct Track { 328 912 #[prost(string, tag = "1")]
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+90
crates/rpc/src/device.rs
··· 1 + use crate::{ 2 + api::rockbox::v1alpha1::{ 3 + device_service_server::DeviceService, ConnectDeviceRequest, ConnectDeviceResponse, 4 + DisconnectDeviceRequest, DisconnectDeviceResponse, GetDeviceRequest, GetDeviceResponse, 5 + GetDevicesRequest, GetDevicesResponse, 6 + }, 7 + rockbox_url, 8 + }; 9 + 10 + pub struct Device { 11 + client: reqwest::Client, 12 + } 13 + 14 + impl Device { 15 + pub fn new(client: reqwest::Client) -> Self { 16 + Self { client } 17 + } 18 + } 19 + 20 + #[tonic::async_trait] 21 + impl DeviceService for Device { 22 + async fn get_devices( 23 + &self, 24 + _request: tonic::Request<GetDevicesRequest>, 25 + ) -> Result<tonic::Response<GetDevicesResponse>, tonic::Status> { 26 + let url = format!("{}/devices", rockbox_url()); 27 + let response = self 28 + .client 29 + .get(&url) 30 + .send() 31 + .await 32 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 33 + let response = response 34 + .json::<Vec<rockbox_types::device::Device>>() 35 + .await 36 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 37 + Ok(tonic::Response::new(GetDevicesResponse { 38 + devices: response.into_iter().map(|d| d.into()).collect(), 39 + })) 40 + } 41 + 42 + async fn get_device( 43 + &self, 44 + request: tonic::Request<GetDeviceRequest>, 45 + ) -> Result<tonic::Response<GetDeviceResponse>, tonic::Status> { 46 + let id = request.into_inner().id; 47 + let url = format!("{}/devices/{}", rockbox_url(), id); 48 + let response = self 49 + .client 50 + .get(&url) 51 + .send() 52 + .await 53 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 54 + let response = response 55 + .json::<Option<rockbox_types::device::Device>>() 56 + .await 57 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 58 + Ok(tonic::Response::new(GetDeviceResponse { 59 + device: response.map(|d| d.into()), 60 + })) 61 + } 62 + 63 + async fn connect_device( 64 + &self, 65 + request: tonic::Request<ConnectDeviceRequest>, 66 + ) -> Result<tonic::Response<ConnectDeviceResponse>, tonic::Status> { 67 + let id = request.into_inner().id; 68 + let url = format!("{}/devices/{}/connect", rockbox_url(), id); 69 + self.client 70 + .put(&url) 71 + .send() 72 + .await 73 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 74 + Ok(tonic::Response::new(ConnectDeviceResponse::default())) 75 + } 76 + 77 + async fn disconnect_device( 78 + &self, 79 + request: tonic::Request<DisconnectDeviceRequest>, 80 + ) -> Result<tonic::Response<DisconnectDeviceResponse>, tonic::Status> { 81 + let id = request.into_inner().id; 82 + let url = format!("{}/devices/{}/disconnect", rockbox_url(), id); 83 + self.client 84 + .put(&url) 85 + .send() 86 + .await 87 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 88 + Ok(tonic::Response::new(DisconnectDeviceResponse::default())) 89 + } 90 + }
+21 -1
crates/rpc/src/lib.rs
··· 3 3 use tokio::fs; 4 4 5 5 pub mod browse; 6 + pub mod device; 6 7 pub mod library; 7 8 pub mod metadata; 8 9 pub mod playback; ··· 34 35 use tantivy::schema::*; 35 36 use tantivy::TantivyDocument; 36 37 use v1alpha1::{ 37 - Album, Artist, CurrentTrackResponse, Entry, GetGlobalSettingsResponse, 38 + Album, Artist, CurrentTrackResponse, Device, Entry, GetGlobalSettingsResponse, 38 39 GetGlobalStatusResponse, NextTrackResponse, SaveSettingsRequest, SearchResponse, Track, 39 40 }; 40 41 ··· 1153 1154 preamp: settings.preamp, 1154 1155 } 1155 1156 }), 1157 + } 1158 + } 1159 + } 1160 + 1161 + impl From<rockbox_types::device::Device> for Device { 1162 + fn from(device: rockbox_types::device::Device) -> Self { 1163 + Self { 1164 + id: device.id, 1165 + name: device.name, 1166 + host: device.host, 1167 + port: device.port as u32, 1168 + ip: device.ip, 1169 + service: device.service, 1170 + app: device.app, 1171 + is_connected: device.is_connected, 1172 + base_url: device.base_url, 1173 + is_cast_device: device.is_cast_device, 1174 + is_source_device: device.is_source_device, 1175 + is_current_device: device.is_current_device, 1156 1176 } 1157 1177 } 1158 1178 }
+5
crates/rpc/src/server.rs
··· 3 3 use std::sync::{Arc, Mutex}; 4 4 5 5 use crate::api::rockbox::v1alpha1::browse_service_server::BrowseServiceServer; 6 + use crate::api::rockbox::v1alpha1::device_service_server::DeviceServiceServer; 6 7 use crate::api::rockbox::v1alpha1::library_service_server::LibraryServiceServer; 7 8 use crate::api::rockbox::v1alpha1::playback_service_server::PlaybackServiceServer; 8 9 use crate::api::rockbox::v1alpha1::playlist_service_server::PlaylistServiceServer; ··· 10 11 use crate::api::rockbox::v1alpha1::sound_service_server::SoundServiceServer; 11 12 use crate::api::rockbox::FILE_DESCRIPTOR_SET; 12 13 use crate::browse::Browse; 14 + use crate::device::Device; 13 15 use crate::library::Library; 14 16 use crate::playback::Playback; 15 17 use crate::playlist::Playlist; ··· 42 44 .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) 43 45 .build_v1alpha()?, 44 46 ) 47 + .add_service(tonic_web::enable(DeviceServiceServer::new(Device::new( 48 + client.clone(), 49 + )))) 45 50 .add_service(tonic_web::enable(LibraryServiceServer::new(Library::new( 46 51 pool.clone(), 47 52 client.clone(),
+5
crates/server/Cargo.toml
··· 9 9 [dependencies] 10 10 anyhow = "1.0.89" 11 11 async-std = {version = "1.13.0", features = ["unstable"]} 12 + futures-util = "0.3.31" 12 13 md5 = "0.7.0" 13 14 owo-colors = "4.0.0" 14 15 queryst = "3.0.0" 15 16 rand = "0.8.5" 16 17 reqwest = {version = "0.12.7", features = ["blocking", "rustls-tls"], default-features = false} 18 + rockbox-chromecast = {path = "../chromecast"} 19 + rockbox-discovery = {path = "../discovery"} 17 20 rockbox-graphql = {path = "../graphql"} 18 21 rockbox-library = {path = "../library"} 19 22 rockbox-mpd = {path = "../mpd"} ··· 22 25 rockbox-search = {path = "../search"} 23 26 rockbox-settings = {path = "../settings"} 24 27 rockbox-sys = {path = "../sys"} 28 + rockbox-tracklist = {path = "../tracklist"} 29 + rockbox-traits = {path = "../traits"} 25 30 rockbox-types = {path = "../types"} 26 31 serde = {version = "1.0.210", features = ["derive"]} 27 32 serde_json = "1.0.128"
+54
crates/server/src/handlers/devices.rs
··· 1 + use anyhow::Error; 2 + use rockbox_chromecast::Chromecast; 3 + 4 + use crate::http::{Context, Request, Response}; 5 + 6 + pub async fn connect(ctx: &Context, req: &Request, res: &mut Response) -> Result<(), Error> { 7 + let id = &req.params[0]; 8 + let mut player = ctx.player.lock().unwrap(); 9 + let mut current_device = ctx.current_device.lock().unwrap(); 10 + let devices = ctx.devices.lock().unwrap(); 11 + let device = devices.iter().find(|d| d.id == *id); 12 + if let Some(device) = device { 13 + *player = Chromecast::connect(device.clone())?; 14 + *current_device = Some(device.clone()); 15 + res.set_status(200); 16 + return Ok(()); 17 + } 18 + res.set_status(404); 19 + Ok(()) 20 + } 21 + 22 + pub async fn disconnect(ctx: &Context, req: &Request, res: &mut Response) -> Result<(), Error> { 23 + let _id = &req.params[0]; 24 + let mut player = ctx.player.lock().unwrap(); 25 + let mut current_device = ctx.current_device.lock().unwrap(); 26 + if let Some(player) = player.as_mut() { 27 + player.disconnect().await?; 28 + } 29 + *player = None; 30 + *current_device = None; 31 + res.set_status(200); 32 + Ok(()) 33 + } 34 + 35 + pub async fn get_devices(ctx: &Context, _req: &Request, res: &mut Response) -> Result<(), Error> { 36 + let devices = ctx.devices.lock().unwrap(); 37 + res.json(&devices.clone()); 38 + Ok(()) 39 + } 40 + 41 + pub async fn get_device(ctx: &Context, req: &Request, res: &mut Response) -> Result<(), Error> { 42 + let id = &req.params[0]; 43 + let devices = ctx.devices.lock().unwrap(); 44 + let device = devices.iter().find(|d| d.id == *id); 45 + 46 + if let Some(device) = device { 47 + res.json(&device.clone()); 48 + return Ok(()); 49 + } 50 + 51 + res.json(&device); 52 + res.set_status(404); 53 + Ok(()) 54 + }
+5
crates/server/src/handlers/mod.rs
··· 1 1 pub mod albums; 2 2 pub mod artists; 3 3 pub mod browse; 4 + pub mod devices; 4 5 pub mod docs; 5 6 pub mod player; 6 7 pub mod playlists; ··· 67 68 async_handler!(docs, get_openapi); 68 69 async_handler!(docs, index); 69 70 async_handler!(search, search); 71 + async_handler!(devices, connect); 72 + async_handler!(devices, disconnect); 73 + async_handler!(devices, get_devices); 74 + async_handler!(devices, get_device);
+25
crates/server/src/http.rs
··· 5 5 self as rb, 6 6 types::{mp3_entry::Mp3Entry, tree::Entry}, 7 7 }; 8 + use rockbox_traits::Player; 9 + use rockbox_types::device::Device; 8 10 use serde::Serialize; 9 11 use serde_json::Value; 10 12 use sqlx::Sqlite; ··· 17 19 time::Duration, 18 20 }; 19 21 use threadpool::ThreadPool; 22 + 23 + use crate::scan::scan_chromecast_devices; 20 24 21 25 type Handler = fn(&Context, &Request, &mut Response) -> Result<(), Error>; 22 26 ··· 25 29 pub fs_cache: Arc<tokio::sync::Mutex<HashMap<String, Vec<Entry>>>>, 26 30 pub metadata_cache: Arc<tokio::sync::Mutex<HashMap<String, Mp3Entry>>>, 27 31 pub indexes: Indexes, 32 + pub devices: Arc<Mutex<Vec<Device>>>, 33 + pub current_device: Arc<Mutex<Option<Device>>>, 34 + pub player: Arc<Mutex<Option<Box<dyn Player + Send>>>>, 28 35 } 29 36 30 37 #[derive(Debug)] ··· 238 245 let db_pool = rt.block_on(rockbox_library::create_connection_pool())?; 239 246 let fs_cache = Arc::new(tokio::sync::Mutex::new(HashMap::new())); 240 247 let metadata_cache = Arc::new(tokio::sync::Mutex::new(HashMap::new())); 248 + let devices = Arc::new(Mutex::new(Vec::new())); 249 + let current_device = Arc::new(Mutex::new(None)); 250 + let player = Arc::new(Mutex::new(None)); 251 + 252 + // Start scanning for devices 253 + scan_chromecast_devices(devices.clone()); 241 254 242 255 let indexes = create_indexes()?; 243 256 ··· 254 267 let cloned_fs_cache = fs_cache.clone(); 255 268 let cloned_metadata_cache = metadata_cache.clone(); 256 269 let cloned_indexes = indexes.clone(); 270 + let cloned_devices = devices.clone(); 271 + let cloned_current_device = current_device.clone(); 272 + let cloned_player = player.clone(); 257 273 pool.execute(move || { 258 274 let mut buf_reader = BufReader::new(&stream); 259 275 let mut request = String::new(); ··· 320 336 cloned_fs_cache, 321 337 cloned_metadata_cache, 322 338 cloned_indexes, 339 + cloned_devices, 340 + cloned_current_device, 341 + cloned_player, 323 342 ); 324 343 } 325 344 ··· 364 383 fs_cache: Arc<tokio::sync::Mutex<HashMap<String, Vec<Entry>>>>, 365 384 metadata_cache: Arc<tokio::sync::Mutex<HashMap<String, Mp3Entry>>>, 366 385 indexes: Indexes, 386 + devices: Arc<Mutex<Vec<Device>>>, 387 + current_device: Arc<Mutex<Option<Device>>>, 388 + player: Arc<Mutex<Option<Box<dyn Player + Send>>>>, 367 389 ) { 368 390 println!("{} {}", method.bright_cyan(), path); 369 391 match self.router.route(method, path) { ··· 374 396 fs_cache, 375 397 metadata_cache, 376 398 indexes, 399 + devices, 400 + current_device, 401 + player, 377 402 }; 378 403 let request = Request { 379 404 method: method.to_string(),
+6
crates/server/src/lib.rs
··· 21 21 pub mod cache; 22 22 pub mod handlers; 23 23 pub mod http; 24 + pub mod scan; 24 25 25 26 pub const AUDIO_EXTENSIONS: [&str; 17] = [ 26 27 "mp3", "ogg", "flac", "m4a", "aac", "mp4", "alac", "wav", "wv", "mpc", "aiff", "ac3", "opus", ··· 90 91 app.put("/settings", update_global_settings); 91 92 app.put("/scan-library", scan_library); 92 93 app.get("/search", search); 94 + 95 + app.get("/devices", get_devices); 96 + app.get("/devices/:id", get_device); 97 + app.put("/devices/:id/connect", connect); 98 + app.put("/devices/:id/disconnect", disconnect); 93 99 94 100 app.get("/", index); 95 101 app.get("/operations/:id", index);
+21
crates/server/src/scan.rs
··· 1 + use futures_util::StreamExt; 2 + use rockbox_discovery::{discover, CHROMECAST_SERVICE_NAME}; 3 + use rockbox_graphql::simplebroker::SimpleBroker; 4 + use rockbox_types::device::Device; 5 + use std::{ 6 + sync::{Arc, Mutex}, 7 + thread, 8 + }; 9 + 10 + pub fn scan_chromecast_devices(devices: Arc<Mutex<Vec<Device>>>) { 11 + thread::spawn(move || { 12 + tokio::runtime::Runtime::new().unwrap().block_on(async { 13 + let services = discover(CHROMECAST_SERVICE_NAME); 14 + tokio::pin!(services); 15 + while let Some(info) = services.next().await { 16 + devices.lock().unwrap().push(Device::from(info.clone())); 17 + SimpleBroker::<Device>::publish(Device::from(info.clone())); 18 + } 19 + }); 20 + }); 21 + }
+8
crates/tracklist/Cargo.toml
··· 1 + [package] 2 + edition = "2021" 3 + name = "rockbox-tracklist" 4 + version = "0.1.0" 5 + 6 + [dependencies] 7 + rand = "0.8.5" 8 + rockbox-traits = {path = "../traits"}
+159
crates/tracklist/src/lib.rs
··· 1 + use rand::seq::SliceRandom; 2 + 3 + use rockbox_traits::types::track::Track; 4 + 5 + #[derive(Default, Debug, Clone, PartialEq)] 6 + pub struct PlaybackState { 7 + pub position_ms: u32, 8 + pub is_playing: bool, 9 + } 10 + 11 + #[derive(Debug, Clone)] 12 + pub struct Tracklist { 13 + tracks: Vec<Track>, 14 + played: Vec<Track>, 15 + current_track: Option<Track>, 16 + playback_state: PlaybackState, 17 + } 18 + 19 + impl Tracklist { 20 + pub fn new(tracks: Vec<Track>) -> Self { 21 + Self { 22 + tracks, 23 + played: Vec::new(), 24 + current_track: None, 25 + playback_state: PlaybackState::default(), 26 + } 27 + } 28 + pub fn new_empty() -> Self { 29 + Self { 30 + tracks: Vec::new(), 31 + played: Vec::new(), 32 + current_track: None, 33 + playback_state: PlaybackState::default(), 34 + } 35 + } 36 + 37 + pub fn add_track(&mut self, track: Track) { 38 + self.tracks.push(track); 39 + } 40 + 41 + pub fn next_track(&mut self) -> Option<Track> { 42 + if self.tracks.is_empty() { 43 + return None; 44 + } 45 + 46 + let next_track = self.tracks.remove(0); 47 + self.current_track = Some(next_track.clone()); 48 + self.played.push(next_track.clone()); 49 + Some(next_track) 50 + } 51 + 52 + pub fn previous_track(&mut self) -> Option<Track> { 53 + if self.played.len() < 2 { 54 + return None; 55 + } 56 + 57 + let previous_track = self.played.pop().unwrap(); 58 + self.tracks.insert(0, previous_track.clone()); 59 + 60 + if self.played.is_empty() { 61 + self.current_track = None; 62 + return None; 63 + } 64 + 65 + let previous_track = self.played.pop().unwrap(); 66 + self.current_track = Some(previous_track.clone()); 67 + 68 + self.played.push(previous_track.clone()); 69 + 70 + Some(previous_track) 71 + } 72 + 73 + pub fn current_track(&self) -> (Option<Track>, usize) { 74 + (self.current_track.clone(), self.played.len()) 75 + } 76 + 77 + pub fn tracks(&self) -> (Vec<Track>, Vec<Track>) { 78 + (self.played.clone(), self.tracks.clone()) 79 + } 80 + 81 + pub fn is_empty(&self) -> bool { 82 + self.tracks.is_empty() 83 + } 84 + 85 + pub fn len(&self) -> usize { 86 + self.tracks.len() 87 + } 88 + 89 + pub fn clear(&mut self) { 90 + self.tracks.clear(); 91 + self.played.clear(); 92 + } 93 + 94 + pub fn remove_track(&mut self, track: Track) { 95 + self.tracks.retain(|t| t.id != track.id); 96 + self.played.retain(|t| t.id != track.id); 97 + } 98 + 99 + pub fn remove_track_at(&mut self, index: usize) { 100 + if index >= self.played.len() { 101 + self.tracks.remove(index - self.played.len()); 102 + return; 103 + } 104 + self.played.remove(index); 105 + } 106 + 107 + pub fn insert(&mut self, index: usize, track: Track) { 108 + self.tracks.insert(index, track); 109 + } 110 + 111 + pub fn insert_tracks(&mut self, index: usize, tracks: Vec<Track>) { 112 + self.tracks.splice(index..index, tracks); 113 + } 114 + 115 + pub fn insert_next(&mut self, track: Track) { 116 + self.tracks.insert(0, track); 117 + } 118 + 119 + pub fn queue(&mut self, tracks: Vec<Track>) { 120 + self.tracks.extend(tracks); 121 + } 122 + 123 + pub fn shuffle(&mut self) { 124 + self.tracks.shuffle(&mut rand::thread_rng()); 125 + } 126 + 127 + pub fn play_track_at(&mut self, index: usize) -> (Option<Track>, usize) { 128 + if index >= (self.tracks.len() + self.played.len()) { 129 + return (None, 0); 130 + } 131 + 132 + self.played = [self.played.clone(), self.tracks.clone()].concat(); 133 + self.tracks = self.played.split_off(index); 134 + 135 + if index > 1 && index < self.played.len() - 1 { 136 + self.next_track(); 137 + } 138 + self.next_track(); 139 + self.current_track() 140 + } 141 + 142 + pub fn playback_state(&self) -> PlaybackState { 143 + self.playback_state.clone() 144 + } 145 + 146 + pub fn set_playback_state(&mut self, playback_state: PlaybackState) { 147 + self.playback_state = playback_state; 148 + } 149 + 150 + pub fn stop(&mut self) { 151 + self.current_track = None; 152 + self.playback_state.is_playing = false; 153 + } 154 + 155 + pub fn load_tracks(&mut self, tracks: Vec<Track>) { 156 + self.clear(); 157 + self.tracks = tracks; 158 + } 159 + }
+11 -8
crates/traits/src/lib.rs
··· 1 1 use anyhow::Error; 2 2 use async_trait::async_trait; 3 + use types::{playback::Playback, track::Track}; 4 + 5 + pub mod types; 3 6 4 7 #[async_trait] 5 8 pub trait Player { 6 - async fn play(&self, url: &str) -> Result<(), Error>; 9 + async fn play(&self) -> Result<(), Error>; 7 10 async fn next(&self) -> Result<(), Error>; 8 11 async fn previous(&self) -> Result<(), Error>; 9 12 async fn stop(&self) -> Result<(), Error>; ··· 11 14 async fn resume(&self) -> Result<(), Error>; 12 15 async fn seek(&self, seconds: i32) -> Result<(), Error>; 13 16 async fn volume(&self, level: f32) -> Result<(), Error>; 14 - async fn load_tracks(&self) -> Result<(), Error>; 15 - async fn play_next(&self) -> Result<(), Error>; 16 - async fn load(&self) -> Result<(), Error>; 17 - async fn get_current_playback(&self) -> Result<(), Error>; 18 - async fn get_current_tracklist(&self) -> Result<(), Error>; 19 - async fn play_track_at(&self) -> Result<(), Error>; 20 - async fn remove_track_at(&self) -> Result<(), Error>; 17 + async fn load_tracks(&self, tracks: Vec<Track>, start_index: Option<i32>) -> Result<(), Error>; 18 + async fn play_next(&self, track: Track) -> Result<(), Error>; 19 + async fn load(&mut self, track: Track) -> Result<(), Error>; 20 + async fn get_current_playback(&mut self) -> Result<Playback, Error>; 21 + async fn get_current_tracklist(&self) -> Result<(Vec<Track>, Vec<Track>), Error>; 22 + async fn play_track_at(&self, position: u32) -> Result<(), Error>; 23 + async fn remove_track_at(&self, position: u32) -> Result<(), Error>; 21 24 async fn disconnect(&self) -> Result<(), Error>; 22 25 } 23 26
+3 -1
crates/types/Cargo.toml
··· 4 4 version = "0.1.0" 5 5 6 6 [dependencies] 7 + local-ip-addr = "0.1.1" 8 + mdns-sd = "0.5.9" 7 9 rockbox-search = {path = "../search"} 10 + rockbox-traits = {path = "../traits"} 8 11 serde = {version = "1.0.213", features = ["derive"]} 9 -
+139 -1
crates/types/src/device.rs
··· 1 - #[derive(Default, Clone)] 1 + use local_ip_addr::get_local_ip_address; 2 + use mdns_sd::ServiceInfo; 3 + use serde::{Deserialize, Serialize}; 4 + 5 + pub const CHROMECAST_SERVICE_NAME: &str = "_googlecast._tcp.local."; 6 + pub const AIRPLAY_SERVICE_NAME: &str = "_raop._tcp.local."; 7 + pub const ROCKBOX_SERVICE_NAME: &str = "_rockbox._tcp.local."; 8 + pub const XBMC_SERVICE_NAME: &str = "_xbmc-jsonrpc-h._tcp.local."; 9 + 10 + pub const AIRPLAY_DEVICE: &str = "AirPlay"; 11 + pub const CHROMECAST_DEVICE: &str = "Chromecast"; 12 + pub const XBMC_DEVICE: &str = "XBMC"; 13 + pub const MUSIC_PLAYER_DEVICE: &str = "MusicPlayer"; 14 + pub const UPNP_DLNA_DEVICE: &str = "UPnP/DLNA"; 15 + pub const ROCKBOX_DEVICE: &str = "Rockbox"; 16 + 17 + #[derive(Default, Clone, Serialize, Deserialize)] 2 18 pub struct Device { 3 19 pub id: String, 4 20 pub name: String, ··· 20 36 self.clone() 21 37 } 22 38 } 39 + 40 + impl From<ServiceInfo> for Device { 41 + fn from(srv: ServiceInfo) -> Self { 42 + if srv.get_fullname().contains("xbmc") { 43 + return Self { 44 + id: srv.get_fullname().to_owned(), 45 + name: srv 46 + .get_fullname() 47 + .replace(XBMC_SERVICE_NAME, "") 48 + .replace(".", "") 49 + .to_owned(), 50 + host: srv 51 + .get_hostname() 52 + .split_at(srv.get_hostname().len() - 1) 53 + .0 54 + .to_owned(), 55 + ip: srv.get_addresses().iter().next().unwrap().to_string(), 56 + port: srv.get_port(), 57 + service: srv.get_fullname().to_owned(), 58 + app: "xbmc".to_owned(), 59 + is_connected: false, 60 + base_url: None, 61 + is_cast_device: true, 62 + is_source_device: true, 63 + is_current_device: false, 64 + }; 65 + } 66 + 67 + if srv.get_fullname().contains(ROCKBOX_SERVICE_NAME) { 68 + let device_id = srv 69 + .get_fullname() 70 + .replace(ROCKBOX_SERVICE_NAME, "") 71 + .split("-") 72 + .collect::<Vec<&str>>()[1] 73 + .replace(".", "") 74 + .to_owned(); 75 + 76 + let is_current_device = device_id == "device_id" 77 + && srv.get_fullname().split("-").collect::<Vec<&str>>()[0].to_owned() == "http"; 78 + 79 + let mut addresses = srv.get_addresses().iter(); 80 + let mut ip = addresses.next().unwrap().to_string(); 81 + 82 + if is_current_device { 83 + ip = get_local_ip_address().unwrap(); 84 + } 85 + 86 + return Self { 87 + id: device_id.clone(), 88 + name: srv 89 + .get_properties() 90 + .get("device_name") 91 + .unwrap_or(&device_id.clone()) 92 + .to_string(), 93 + host: srv 94 + .get_hostname() 95 + .split_at(srv.get_hostname().len() - 1) 96 + .0 97 + .to_owned(), 98 + ip, 99 + port: srv.get_port(), 100 + service: srv.get_fullname().split("-").collect::<Vec<&str>>()[0].to_owned(), 101 + app: "rockbox".to_owned(), 102 + is_connected: false, 103 + base_url: None, 104 + is_cast_device: true, 105 + is_source_device: true, 106 + is_current_device, 107 + }; 108 + } 109 + 110 + if srv.get_fullname().contains(CHROMECAST_SERVICE_NAME) { 111 + return Self { 112 + id: srv.get_properties().get("id").unwrap().to_owned(), 113 + name: srv.get_properties().get("fn").unwrap().to_owned(), 114 + host: srv 115 + .get_hostname() 116 + .split_at(srv.get_hostname().len() - 1) 117 + .0 118 + .to_owned(), 119 + ip: srv.get_addresses().iter().next().unwrap().to_string(), 120 + port: srv.get_port(), 121 + service: srv.get_fullname().to_owned(), 122 + app: "chromecast".to_owned(), 123 + is_connected: false, 124 + base_url: None, 125 + is_cast_device: true, 126 + is_source_device: false, 127 + is_current_device: false, 128 + }; 129 + } 130 + 131 + if srv.get_fullname().contains(AIRPLAY_SERVICE_NAME) { 132 + let name = srv.get_fullname().split("@").collect::<Vec<&str>>()[1] 133 + .replace(AIRPLAY_SERVICE_NAME, "") 134 + .to_owned(); 135 + let name = name.split_at(name.len() - 1).0.to_owned(); 136 + return Self { 137 + id: srv.get_fullname().to_owned(), 138 + name, 139 + host: srv 140 + .get_hostname() 141 + .split_at(srv.get_hostname().len() - 1) 142 + .0 143 + .to_owned(), 144 + ip: srv.get_addresses().iter().next().unwrap().to_string(), 145 + port: srv.get_port(), 146 + service: srv.get_fullname().to_owned(), 147 + app: "airplay".to_owned(), 148 + is_connected: false, 149 + base_url: None, 150 + is_cast_device: true, 151 + is_source_device: false, 152 + is_current_device: false, 153 + }; 154 + } 155 + 156 + Self { 157 + ..Default::default() 158 + } 159 + } 160 + }