mirror of
https://github.com/danbulant/despot
synced 2026-06-19 06:21:01 +00:00
progress on integrated player
This commit is contained in:
parent
8cf183e5c8
commit
35139b84a9
8 changed files with 570 additions and 71 deletions
209
Cargo.lock
generated
209
Cargo.lock
generated
|
|
@ -84,6 +84,28 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7a3dc3ad32931b2d6e97c99a702208dfd1e2c446580e5f99d1d8355df26db6"
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
|
|
@ -98,7 +120,7 @@ dependencies = [
|
|||
"jni-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
"ndk 0.9.0",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum",
|
||||
|
|
@ -542,7 +564,7 @@ version = "0.23.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad3a619a9de81e1d7de1f1186dcba4506ed661a0e483d84410fdef0ee87b2f96"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bindgen 0.69.5",
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
|
|
@ -608,7 +630,7 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
|
|
@ -622,6 +644,24 @@ dependencies = [
|
|||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote 1.0.37",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.6.0"
|
||||
|
|
@ -1079,6 +1119,26 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-sys"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b"
|
||||
dependencies = [
|
||||
"bindgen 0.70.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-text"
|
||||
version = "0.12.1"
|
||||
|
|
@ -1102,6 +1162,29 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk 0.8.0",
|
||||
"ndk-context",
|
||||
"oboe",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows 0.54.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.15"
|
||||
|
|
@ -1309,6 +1392,12 @@ dependencies = [
|
|||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.6.0"
|
||||
|
|
@ -1412,7 +1501,9 @@ dependencies = [
|
|||
"http-cache-reqwest",
|
||||
"image",
|
||||
"itertools 0.10.5",
|
||||
"librespot-connect",
|
||||
"librespot-core",
|
||||
"librespot-metadata",
|
||||
"librespot-oauth",
|
||||
"librespot-playback",
|
||||
"librespot-protocol",
|
||||
|
|
@ -2985,7 +3076,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "librespot-audio"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bytes",
|
||||
|
|
@ -3002,10 +3093,30 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librespot-connect"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"librespot-core",
|
||||
"librespot-playback",
|
||||
"librespot-protocol",
|
||||
"log",
|
||||
"protobuf",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librespot-core"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -3062,7 +3173,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "librespot-metadata"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -3079,7 +3190,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "librespot-oauth"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"log",
|
||||
"oauth2",
|
||||
|
|
@ -3090,8 +3201,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "librespot-playback"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"futures-util",
|
||||
"librespot-audio",
|
||||
"librespot-core",
|
||||
|
|
@ -3100,6 +3212,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
"rodio",
|
||||
"shell-words",
|
||||
"symphonia",
|
||||
"thiserror",
|
||||
|
|
@ -3110,7 +3223,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "librespot-protocol"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#1b9192b52a983f5aca2c145621393ac0d12ace1a"
|
||||
source = "git+https://github.com/photovoltex/librespot.git?branch=integrate-dealer#7be55c96ec36f944bb7dc0799b6697c874d59498"
|
||||
dependencies = [
|
||||
"protobuf",
|
||||
"protobuf-codegen",
|
||||
|
|
@ -3191,6 +3304,15 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
|
|
@ -3415,6 +3537,20 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys 0.5.0+25.2.9519653",
|
||||
"num_enum",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
|
|
@ -3883,6 +4019,29 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
||||
dependencies = [
|
||||
"jni",
|
||||
"ndk 0.8.0",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"oboe-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe-sys"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
|
|
@ -4808,6 +4967,16 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rodio"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
|
|
@ -6802,6 +6971,16 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||
dependencies = [
|
||||
"windows-core 0.54.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.57.0"
|
||||
|
|
@ -6831,6 +7010,16 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||
dependencies = [
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
|
|
@ -7175,7 +7364,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"libc",
|
||||
"memmap2 0.9.5",
|
||||
"ndk",
|
||||
"ndk 0.9.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
|
|
|
|||
22
Cargo.toml
22
Cargo.toml
|
|
@ -6,13 +6,18 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
# cushy = { version = "0.4.0", features=["tokio", "tokio-multi-thread", "plotters", "roboto-flex"], default-features = false }
|
||||
cushy = { git = "https://github.com/khonsulabs/cushy.git", branch = "main", features=["tokio", "tokio-multi-thread", "plotters", "roboto-flex"] }
|
||||
cushy = { git = "https://github.com/khonsulabs/cushy.git", branch = "main", features = [
|
||||
"tokio",
|
||||
"tokio-multi-thread",
|
||||
"plotters",
|
||||
"roboto-flex",
|
||||
] }
|
||||
tokio = { version = "1.40.0", features = ["rt", "rt-multi-thread"] }
|
||||
plotters = { version = "0.3.7", default-features = false }
|
||||
image = { version = "0.25.0", features = ["png"] }
|
||||
mpris = "2.0.1"
|
||||
reqwest = "0.12.8"
|
||||
reqwest-middleware="0.3.3"
|
||||
reqwest-middleware = "0.3.3"
|
||||
http-cache-reqwest = "0.14.0"
|
||||
color_quant = "1.0"
|
||||
hsl = "0.1.1"
|
||||
|
|
@ -22,9 +27,18 @@ clap = { version = "4.5.20", features = ["derive"] }
|
|||
chrono = "0.4"
|
||||
librespot-core = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
librespot-oauth = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
librespot-playback = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
librespot-playback = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer", features = [
|
||||
"rodio-backend",
|
||||
] }
|
||||
librespot-protocol = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
futures-util = { version = "0.3", features = ["alloc", "bilock", "sink", "unstable"] }
|
||||
librespot-connect = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
librespot-metadata = { git = "https://github.com/photovoltex/librespot.git", branch = "integrate-dealer" }
|
||||
futures-util = { version = "0.3", features = [
|
||||
"alloc",
|
||||
"bilock",
|
||||
"sink",
|
||||
"unstable",
|
||||
] }
|
||||
rspotify = { version = "0.13.3" }
|
||||
oauth2 = "4.4"
|
||||
|
||||
|
|
|
|||
28
flake.lock
28
flake.lock
|
|
@ -105,11 +105,11 @@
|
|||
"pre-commit-hooks": "pre-commit-hooks_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723311214,
|
||||
"narHash": "sha256-xdGZQBEa1AC2us/sY3igS/CucWY6jErXsAvCFRhB2LI=",
|
||||
"lastModified": 1732039290,
|
||||
"narHash": "sha256-LQKY7bShf2H9kJouxa9ZspfdrulnZF9o4kLTqGqCDYM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "crate2nix",
|
||||
"rev": "236f6addfd452a48be805819e3216af79e988fd5",
|
||||
"rev": "9ff208ce7f5a482272b1bcefbe363c772d7ff914",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -393,11 +393,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727826117,
|
||||
"narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
|
||||
"lastModified": 1730504689,
|
||||
"narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
|
||||
"rev": "506278e768c2a08bec68eb62932193e341f55c90",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -704,8 +704,8 @@
|
|||
"nixpkgs_7": {
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-yumd4fBc/hi8a9QgA9IT8vlQuLZ2oqhkJXHPKxH/tRw=",
|
||||
"path": "/nix/store/rs4fjbnw4qx7ns2hzzrz2iz52va7vs5z-source",
|
||||
"narHash": "sha256-mwrFF0vElHJP8X3pFCByJR365Q2463ATp2qGIrDUdlE=",
|
||||
"path": "/nix/store/dxdcvjnvz3b91gvsrhpb7gp156nnj8bf-source",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -715,11 +715,11 @@
|
|||
},
|
||||
"nixpkgs_8": {
|
||||
"locked": {
|
||||
"lastModified": 1718428119,
|
||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||
"lastModified": 1728538411,
|
||||
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -843,11 +843,11 @@
|
|||
"nixpkgs": "nixpkgs_8"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728354625,
|
||||
"narHash": "sha256-r+Sa1NRRT7LXKzCaVaq75l1GdZcegODtF06uaxVVVbI=",
|
||||
"lastModified": 1732069891,
|
||||
"narHash": "sha256-moKx8AVJrViCSdA0e0nSsG8b1dAsObI4sRAtbqbvBY8=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "d216ade5a0091ce60076bf1f8bc816433a1fc5da",
|
||||
"rev": "8509a51241c407d583b1963d5079585a992506e8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
88
src/main.rs
88
src/main.rs
|
|
@ -1,4 +1,4 @@
|
|||
use std::thread;
|
||||
use std::{sync::Arc, thread};
|
||||
|
||||
use api::{SpotifyContext, SpotifyContextRef};
|
||||
use auth::get_token;
|
||||
|
|
@ -8,19 +8,28 @@ use cushy::{
|
|||
value::Dynamic, widget::MakeWidget, window::MakeWindow, Application, Open, PendingApp, Run,
|
||||
TokioRuntime,
|
||||
};
|
||||
use librespot_connect::{
|
||||
spirc::{Spirc, SpircLoadCommand},
|
||||
state::ConnectStateConfig,
|
||||
};
|
||||
use librespot_core::{authentication::Credentials, Session, SessionConfig};
|
||||
use librespot_playback::{
|
||||
audio_backend,
|
||||
config::{AudioFormat, PlayerConfig},
|
||||
mixer::NoOpVolume,
|
||||
player::Player,
|
||||
mixer::{softmixer::SoftMixer, Mixer, MixerConfig, NoOpVolume},
|
||||
player::{Player, PlayerEvent},
|
||||
};
|
||||
use player::{new_dynamic_player, DynamicPlayer, DynamicPlayerInner};
|
||||
use widgets::{
|
||||
library::playlist::playlists_widget, pages::liked::LikedSongsPage, playback::bar::bar,
|
||||
ActivePage,
|
||||
};
|
||||
use widgets::{library::playlist::playlists_widget, pages::liked::LikedSongsPage, ActivePage};
|
||||
|
||||
mod api;
|
||||
mod auth;
|
||||
mod cli;
|
||||
mod nodebug;
|
||||
mod player;
|
||||
mod rt;
|
||||
mod theme;
|
||||
mod vibrancy;
|
||||
|
|
@ -28,7 +37,7 @@ mod widgets;
|
|||
|
||||
fn main() -> cushy::Result {
|
||||
let args = Args::parse();
|
||||
let mut app = PendingApp::new(TokioRuntime::default());
|
||||
let app = PendingApp::new(TokioRuntime::default());
|
||||
|
||||
let token = get_token().unwrap();
|
||||
|
||||
|
|
@ -36,6 +45,7 @@ fn main() -> cushy::Result {
|
|||
let player_config = PlayerConfig::default();
|
||||
let audio_format = AudioFormat::default();
|
||||
let credentials = Credentials::with_access_token(&token.access_token);
|
||||
let connect_config = ConnectStateConfig::default();
|
||||
let backend = audio_backend::find(None).unwrap();
|
||||
|
||||
let session;
|
||||
|
|
@ -44,6 +54,8 @@ fn main() -> cushy::Result {
|
|||
let guard = app.cushy().enter_runtime();
|
||||
session = Session::new(session_config, None);
|
||||
|
||||
dbg!(session.user_data());
|
||||
|
||||
let player = Player::new(
|
||||
player_config,
|
||||
session.clone(),
|
||||
|
|
@ -51,48 +63,42 @@ fn main() -> cushy::Result {
|
|||
move || backend(None, audio_format),
|
||||
);
|
||||
|
||||
tokio::spawn({
|
||||
let session = session.clone();
|
||||
async move {
|
||||
if let Err(e) = session.connect(credentials, false).await {
|
||||
println!("Error connecting: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut channel = player.get_player_event_channel();
|
||||
loop {
|
||||
let event = channel.blocking_recv();
|
||||
if let Some(event) = event {
|
||||
dbg!(event);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dbg!(session.user_data());
|
||||
|
||||
let context = SpotifyContextRef::new(SpotifyContext::new(session, token));
|
||||
let context = SpotifyContextRef::new(SpotifyContext::new(session.clone(), token));
|
||||
|
||||
let mut app = app.as_app();
|
||||
tokio::spawn(async move {
|
||||
let user = context.current_user().await.unwrap();
|
||||
dbg!(&user);
|
||||
let userid = user.id;
|
||||
let (_spirc, spirc_task) = Spirc::new(
|
||||
connect_config,
|
||||
session.clone(),
|
||||
credentials,
|
||||
player.clone(),
|
||||
Arc::new(SoftMixer::open(MixerConfig::default())),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let dynplayer = new_dynamic_player(player);
|
||||
// this cannot happen in `{}` inside join for some reason
|
||||
let dynplayer2 = dynplayer.clone();
|
||||
tokio::join!(spirc_task, dynplayer2.run(), async move {
|
||||
let user = context.current_user().await.unwrap();
|
||||
dbg!(&user);
|
||||
// let userid = user.id;
|
||||
|
||||
let playlists = context.current_user_playlists(None, None).await.unwrap();
|
||||
let playlists = context.current_user_playlists(None, None).await.unwrap();
|
||||
|
||||
let selected_page = Dynamic::new(ActivePage::default());
|
||||
let selected_page = Dynamic::new(ActivePage::default());
|
||||
|
||||
playlists_widget(playlists.items, selected_page)
|
||||
.and(LikedSongsPage::new(context.clone()).into_widget())
|
||||
.into_columns()
|
||||
.expand()
|
||||
.make_window()
|
||||
.open(&mut app)
|
||||
.unwrap();
|
||||
playlists_widget(playlists.items, selected_page)
|
||||
.and(LikedSongsPage::new(context.clone()).into_widget())
|
||||
.into_columns()
|
||||
.expand()
|
||||
.and(bar(dynplayer))
|
||||
.into_rows()
|
||||
.expand()
|
||||
.make_window()
|
||||
.open(&mut app)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
drop(guard);
|
||||
|
|
|
|||
193
src/player.rs
Normal file
193
src/player.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use cushy::value::{Destination, Dynamic};
|
||||
use librespot_metadata::audio::AudioItem;
|
||||
use librespot_playback::player::{Player, PlayerEvent};
|
||||
|
||||
pub type DynamicPlayer = Arc<DynamicPlayerInner>;
|
||||
|
||||
pub struct DynamicPlayerInner {
|
||||
player: Arc<Player>,
|
||||
pub state: Dynamic<PlayerState>,
|
||||
pub track: Dynamic<Option<Box<AudioItem>>>,
|
||||
pub track_progress: Dynamic<Option<Duration>>,
|
||||
started_at: Arc<Mutex<Option<Instant>>>,
|
||||
pub repeat: Dynamic<RepeatMode>,
|
||||
pub shuffle: Dynamic<bool>,
|
||||
pub volume: Dynamic<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub enum RepeatMode {
|
||||
#[default]
|
||||
None,
|
||||
Track,
|
||||
Context,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub enum PlayerState {
|
||||
Loading {
|
||||
loading_at: Duration,
|
||||
},
|
||||
Playing,
|
||||
Paused {
|
||||
paused_at: Duration,
|
||||
},
|
||||
Stopped,
|
||||
#[default]
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
pub fn new_dynamic_player(player: Arc<Player>) -> DynamicPlayer {
|
||||
Arc::new(DynamicPlayerInner::new(player))
|
||||
}
|
||||
|
||||
impl DynamicPlayerInner {
|
||||
pub fn new(player: Arc<Player>) -> Self {
|
||||
Self {
|
||||
player,
|
||||
repeat: Default::default(),
|
||||
shuffle: Default::default(),
|
||||
started_at: Default::default(),
|
||||
state: Default::default(),
|
||||
track: Default::default(),
|
||||
volume: Default::default(),
|
||||
track_progress: Default::default(),
|
||||
}
|
||||
}
|
||||
pub async fn run(&self) {
|
||||
let mut channel = self.player.get_player_event_channel();
|
||||
let mut id = 0u64;
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = channel.recv() => {
|
||||
if let Some(event) = event {
|
||||
match event {
|
||||
PlayerEvent::Stopped {
|
||||
play_request_id, ..
|
||||
} => {
|
||||
println!("Stopped {play_request_id}");
|
||||
self.state.set(PlayerState::Stopped);
|
||||
}
|
||||
PlayerEvent::Loading {
|
||||
play_request_id,
|
||||
position_ms,
|
||||
track_id,
|
||||
} => {
|
||||
println!("Loading {play_request_id} {position_ms} {track_id}");
|
||||
self.state.set(PlayerState::Loading {
|
||||
loading_at: Duration::from_millis(position_ms as u64),
|
||||
});
|
||||
}
|
||||
PlayerEvent::Playing {
|
||||
play_request_id,
|
||||
position_ms,
|
||||
track_id,
|
||||
} => {
|
||||
println!("Playing {play_request_id} {position_ms} {track_id}");
|
||||
self.state.set(PlayerState::Playing);
|
||||
*self.started_at.lock().unwrap() =
|
||||
Some(Instant::now() - Duration::from_millis(position_ms as u64));
|
||||
}
|
||||
PlayerEvent::Paused {
|
||||
play_request_id,
|
||||
position_ms,
|
||||
track_id,
|
||||
} => {
|
||||
println!("Paused {play_request_id} {position_ms} {track_id}");
|
||||
self.state.set(PlayerState::Paused {
|
||||
paused_at: Duration::from_millis(position_ms as u64),
|
||||
});
|
||||
}
|
||||
PlayerEvent::Unavailable {
|
||||
play_request_id,
|
||||
track_id,
|
||||
} => {
|
||||
println!("Unavailable {play_request_id} {track_id}");
|
||||
}
|
||||
PlayerEvent::VolumeChanged { volume } => {
|
||||
println!("volume {volume}");
|
||||
self.volume.set(volume as f32 / u16::MAX as f32)
|
||||
}
|
||||
PlayerEvent::PositionCorrection {
|
||||
play_request_id,
|
||||
position_ms,
|
||||
track_id,
|
||||
} => {
|
||||
println!("PositionCorrection {play_request_id} {position_ms} {track_id}");
|
||||
*self.started_at.lock().unwrap() =
|
||||
Some(Instant::now() - Duration::from_millis(position_ms as u64));
|
||||
}
|
||||
PlayerEvent::Seeked {
|
||||
play_request_id,
|
||||
position_ms,
|
||||
track_id,
|
||||
} => {
|
||||
println!("Seeked {play_request_id} {position_ms} {track_id}");
|
||||
*self.started_at.lock().unwrap() =
|
||||
Some(Instant::now() - Duration::from_millis(position_ms as u64));
|
||||
}
|
||||
PlayerEvent::TrackChanged { audio_item } => {
|
||||
println!("TrackChanged {}", audio_item.uri);
|
||||
dbg!(&audio_item);
|
||||
*self.track.lock() = Some(audio_item);
|
||||
}
|
||||
PlayerEvent::SessionConnected {
|
||||
connection_id,
|
||||
user_name,
|
||||
} => {
|
||||
println!("SessionConnected {connection_id} {user_name}");
|
||||
self.state.set(PlayerState::Stopped);
|
||||
}
|
||||
PlayerEvent::SessionDisconnected {
|
||||
connection_id,
|
||||
user_name,
|
||||
} => {
|
||||
println!("SessionDisconnected {connection_id} {user_name}");
|
||||
self.state.set(PlayerState::Disconnected);
|
||||
}
|
||||
PlayerEvent::SessionClientChanged {
|
||||
client_brand_name,
|
||||
client_id,
|
||||
client_model_name,
|
||||
client_name,
|
||||
} => {
|
||||
println!("SessionClientChanged {client_brand_name} {client_id} {client_model_name} {client_name}");
|
||||
}
|
||||
PlayerEvent::ShuffleChanged { shuffle } => {
|
||||
println!("ShuffleChanged {shuffle}");
|
||||
self.shuffle.set(shuffle);
|
||||
}
|
||||
PlayerEvent::RepeatChanged { context, track } => {
|
||||
let repeat_mode = match (context, track) {
|
||||
(true, false) => RepeatMode::Context,
|
||||
(false, true) => RepeatMode::Track,
|
||||
_ => RepeatMode::None,
|
||||
};
|
||||
println!("RepeatChanged {repeat_mode:?}");
|
||||
self.repeat.set(repeat_mode);
|
||||
}
|
||||
PlayerEvent::AutoPlayChanged { .. } => {
|
||||
println!("AutoPlayChanged")
|
||||
}
|
||||
PlayerEvent::FilterExplicitContentChanged { .. } => {
|
||||
println!("FilterExplicitContentChanged")
|
||||
}
|
||||
PlayerEvent::PlayRequestIdChanged { play_request_id } => {
|
||||
println!("PlayRequestIdChanged {play_request_id}");
|
||||
id = play_request_id;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,14 @@ pub mod image;
|
|||
pub mod library;
|
||||
pub mod owned;
|
||||
pub mod pages;
|
||||
pub mod playback;
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub enum ActivePage {
|
||||
#[default]
|
||||
LikedSongs,
|
||||
Playlist(SimplifiedPlaylist),
|
||||
Album(SimplifiedAlbum)
|
||||
Album(SimplifiedAlbum),
|
||||
}
|
||||
|
||||
type SelectedPage = Dynamic<ActivePage>;
|
||||
type SelectedPage = Dynamic<ActivePage>;
|
||||
|
|
|
|||
95
src/widgets/playback/bar.rs
Normal file
95
src/widgets/playback/bar.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
use cushy::{
|
||||
figures::{units::Lp, Size},
|
||||
styles::{Dimension, DimensionRange},
|
||||
value::Source,
|
||||
widget::MakeWidget,
|
||||
widgets::{Button, Image, Label},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use librespot_metadata::audio::UniqueFields;
|
||||
|
||||
use crate::{
|
||||
player::{DynamicPlayer, PlayerState},
|
||||
widgets::image::ImageExt,
|
||||
};
|
||||
|
||||
pub fn bar(player: DynamicPlayer) -> impl MakeWidget {
|
||||
meta(player).size(Size {
|
||||
width: DimensionRange::default(),
|
||||
height: Dimension::Lp(Lp::inches_f(1.5)).into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn meta(player: DynamicPlayer) -> impl MakeWidget {
|
||||
Image::new_empty()
|
||||
.with_url(player.track.map_each(|track| {
|
||||
track
|
||||
.as_ref()
|
||||
.map(|track| track.covers.first().map(|cover| cover.url.clone()))
|
||||
.flatten()
|
||||
}))
|
||||
.size(Size::squared(Dimension::Lp(Lp::inches_f(1.))))
|
||||
.and(
|
||||
player
|
||||
.track
|
||||
.map_each(|track| {
|
||||
track
|
||||
.as_ref()
|
||||
.map(|track| {
|
||||
track
|
||||
.name
|
||||
.clone()
|
||||
.and(match &track.unique_fields {
|
||||
UniqueFields::Track {
|
||||
artists,
|
||||
album,
|
||||
album_artists,
|
||||
popularity,
|
||||
number,
|
||||
disc_number,
|
||||
} => {
|
||||
artists.iter().map(|artist| artist.name.clone()).join(", ")
|
||||
}
|
||||
UniqueFields::Episode {
|
||||
description,
|
||||
publish_time,
|
||||
show_name,
|
||||
} => show_name.clone(),
|
||||
})
|
||||
.into_rows()
|
||||
.make_widget()
|
||||
})
|
||||
.unwrap_or(Label::<String>::new("No track found").make_widget())
|
||||
})
|
||||
.expand(),
|
||||
)
|
||||
.into_columns()
|
||||
}
|
||||
|
||||
fn controls(player: DynamicPlayer) -> impl MakeWidget {
|
||||
"shuffle"
|
||||
.into_button()
|
||||
.and("previous".into_button())
|
||||
.and(player.state.map_each(|state| {
|
||||
match state {
|
||||
PlayerState::Playing => "pause",
|
||||
PlayerState::Paused { .. } => "play",
|
||||
_ => "play",
|
||||
}
|
||||
.into_button()
|
||||
.make_widget()
|
||||
}))
|
||||
.and("skip".into_button())
|
||||
.and("repeat".into_button())
|
||||
.into_columns()
|
||||
.and(progress(player))
|
||||
.into_rows()
|
||||
}
|
||||
|
||||
fn progress(player: DynamicPlayer) -> impl MakeWidget {
|
||||
"progress bar here"
|
||||
}
|
||||
|
||||
fn vol(player: DynamicPlayer) -> impl MakeWidget {
|
||||
"vol control here"
|
||||
}
|
||||
1
src/widgets/playback/mod.rs
Normal file
1
src/widgets/playback/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod bar;
|
||||
Loading…
Reference in a new issue