progress on integrated player

This commit is contained in:
Daniel Bulant 2024-11-20 20:01:31 +01:00
parent 8cf183e5c8
commit 35139b84a9
No known key found for this signature in database
8 changed files with 570 additions and 71 deletions

209
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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": {

View file

@ -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
View 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;
}
}
}
}
}
}

View file

@ -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>;

View 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"
}

View file

@ -0,0 +1 @@
pub mod bar;