aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock110
-rw-r--r--Cargo.toml1
-rw-r--r--src/cli.rs11
-rw-r--r--src/daemon.rs14
-rw-r--r--src/queue.rs85
5 files changed, 200 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5893217..52ab8c8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -146,7 +146,7 @@ dependencies = [
146 "futures-lite", 146 "futures-lite",
147 "parking", 147 "parking",
148 "polling", 148 "polling",
149 "rustix", 149 "rustix 1.1.4",
150 "slab", 150 "slab",
151 "windows-sys 0.61.2", 151 "windows-sys 0.61.2",
152] 152]
@@ -177,7 +177,7 @@ dependencies = [
177 "cfg-if", 177 "cfg-if",
178 "event-listener", 178 "event-listener",
179 "futures-lite", 179 "futures-lite",
180 "rustix", 180 "rustix 1.1.4",
181] 181]
182 182
183[[package]] 183[[package]]
@@ -203,7 +203,7 @@ dependencies = [
203 "cfg-if", 203 "cfg-if",
204 "futures-core", 204 "futures-core",
205 "futures-io", 205 "futures-io",
206 "rustix", 206 "rustix 1.1.4",
207 "signal-hook-registry", 207 "signal-hook-registry",
208 "slab", 208 "slab",
209 "windows-sys 0.61.2", 209 "windows-sys 0.61.2",
@@ -1329,6 +1329,12 @@ dependencies = [
1329 1329
1330[[package]] 1330[[package]]
1331name = "linux-raw-sys" 1331name = "linux-raw-sys"
1332version = "0.4.15"
1333source = "registry+https://github.com/rust-lang/crates.io-index"
1334checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
1335
1336[[package]]
1337name = "linux-raw-sys"
1332version = "0.12.1" 1338version = "0.12.1"
1333source = "registry+https://github.com/rust-lang/crates.io-index" 1339source = "registry+https://github.com/rust-lang/crates.io-index"
1334checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 1340checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
@@ -1707,7 +1713,7 @@ dependencies = [
1707 "concurrent-queue", 1713 "concurrent-queue",
1708 "hermit-abi", 1714 "hermit-abi",
1709 "pin-project-lite", 1715 "pin-project-lite",
1710 "rustix", 1716 "rustix 1.1.4",
1711 "windows-sys 0.61.2", 1717 "windows-sys 0.61.2",
1712] 1718]
1713 1719
@@ -1989,6 +1995,7 @@ dependencies = [
1989 "serde_json", 1995 "serde_json",
1990 "symphonia", 1996 "symphonia",
1991 "tempfile", 1997 "tempfile",
1998 "terminal_size",
1992 "tokio", 1999 "tokio",
1993 "toml", 2000 "toml",
1994] 2001]
@@ -2035,6 +2042,19 @@ dependencies = [
2035 2042
2036[[package]] 2043[[package]]
2037name = "rustix" 2044name = "rustix"
2045version = "0.38.44"
2046source = "registry+https://github.com/rust-lang/crates.io-index"
2047checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
2048dependencies = [
2049 "bitflags 2.11.1",
2050 "errno",
2051 "libc",
2052 "linux-raw-sys 0.4.15",
2053 "windows-sys 0.52.0",
2054]
2055
2056[[package]]
2057name = "rustix"
2038version = "1.1.4" 2058version = "1.1.4"
2039source = "registry+https://github.com/rust-lang/crates.io-index" 2059source = "registry+https://github.com/rust-lang/crates.io-index"
2040checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 2060checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
@@ -2042,7 +2062,7 @@ dependencies = [
2042 "bitflags 2.11.1", 2062 "bitflags 2.11.1",
2043 "errno", 2063 "errno",
2044 "libc", 2064 "libc",
2045 "linux-raw-sys", 2065 "linux-raw-sys 0.12.1",
2046 "windows-sys 0.61.2", 2066 "windows-sys 0.61.2",
2047] 2067]
2048 2068
@@ -2595,11 +2615,21 @@ dependencies = [
2595 "fastrand", 2615 "fastrand",
2596 "getrandom 0.4.2", 2616 "getrandom 0.4.2",
2597 "once_cell", 2617 "once_cell",
2598 "rustix", 2618 "rustix 1.1.4",
2599 "windows-sys 0.61.2", 2619 "windows-sys 0.61.2",
2600] 2620]
2601 2621
2602[[package]] 2622[[package]]
2623name = "terminal_size"
2624version = "0.3.0"
2625source = "registry+https://github.com/rust-lang/crates.io-index"
2626checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
2627dependencies = [
2628 "rustix 0.38.44",
2629 "windows-sys 0.48.0",
2630]
2631
2632[[package]]
2603name = "thiserror" 2633name = "thiserror"
2604version = "1.0.69" 2634version = "1.0.69"
2605source = "registry+https://github.com/rust-lang/crates.io-index" 2635source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3310,6 +3340,15 @@ dependencies = [
3310 3340
3311[[package]] 3341[[package]]
3312name = "windows-sys" 3342name = "windows-sys"
3343version = "0.48.0"
3344source = "registry+https://github.com/rust-lang/crates.io-index"
3345checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
3346dependencies = [
3347 "windows-targets 0.48.5",
3348]
3349
3350[[package]]
3351name = "windows-sys"
3313version = "0.52.0" 3352version = "0.52.0"
3314source = "registry+https://github.com/rust-lang/crates.io-index" 3353source = "registry+https://github.com/rust-lang/crates.io-index"
3315checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3354checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -3352,6 +3391,21 @@ dependencies = [
3352 3391
3353[[package]] 3392[[package]]
3354name = "windows-targets" 3393name = "windows-targets"
3394version = "0.48.5"
3395source = "registry+https://github.com/rust-lang/crates.io-index"
3396checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
3397dependencies = [
3398 "windows_aarch64_gnullvm 0.48.5",
3399 "windows_aarch64_msvc 0.48.5",
3400 "windows_i686_gnu 0.48.5",
3401 "windows_i686_msvc 0.48.5",
3402 "windows_x86_64_gnu 0.48.5",
3403 "windows_x86_64_gnullvm 0.48.5",
3404 "windows_x86_64_msvc 0.48.5",
3405]
3406
3407[[package]]
3408name = "windows-targets"
3355version = "0.52.6" 3409version = "0.52.6"
3356source = "registry+https://github.com/rust-lang/crates.io-index" 3410source = "registry+https://github.com/rust-lang/crates.io-index"
3357checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3411checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -3418,6 +3472,12 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
3418 3472
3419[[package]] 3473[[package]]
3420name = "windows_aarch64_gnullvm" 3474name = "windows_aarch64_gnullvm"
3475version = "0.48.5"
3476source = "registry+https://github.com/rust-lang/crates.io-index"
3477checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
3478
3479[[package]]
3480name = "windows_aarch64_gnullvm"
3421version = "0.52.6" 3481version = "0.52.6"
3422source = "registry+https://github.com/rust-lang/crates.io-index" 3482source = "registry+https://github.com/rust-lang/crates.io-index"
3423checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3483checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -3436,6 +3496,12 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
3436 3496
3437[[package]] 3497[[package]]
3438name = "windows_aarch64_msvc" 3498name = "windows_aarch64_msvc"
3499version = "0.48.5"
3500source = "registry+https://github.com/rust-lang/crates.io-index"
3501checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
3502
3503[[package]]
3504name = "windows_aarch64_msvc"
3439version = "0.52.6" 3505version = "0.52.6"
3440source = "registry+https://github.com/rust-lang/crates.io-index" 3506source = "registry+https://github.com/rust-lang/crates.io-index"
3441checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3507checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -3454,6 +3520,12 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
3454 3520
3455[[package]] 3521[[package]]
3456name = "windows_i686_gnu" 3522name = "windows_i686_gnu"
3523version = "0.48.5"
3524source = "registry+https://github.com/rust-lang/crates.io-index"
3525checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
3526
3527[[package]]
3528name = "windows_i686_gnu"
3457version = "0.52.6" 3529version = "0.52.6"
3458source = "registry+https://github.com/rust-lang/crates.io-index" 3530source = "registry+https://github.com/rust-lang/crates.io-index"
3459checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3531checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -3484,6 +3556,12 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
3484 3556
3485[[package]] 3557[[package]]
3486name = "windows_i686_msvc" 3558name = "windows_i686_msvc"
3559version = "0.48.5"
3560source = "registry+https://github.com/rust-lang/crates.io-index"
3561checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
3562
3563[[package]]
3564name = "windows_i686_msvc"
3487version = "0.52.6" 3565version = "0.52.6"
3488source = "registry+https://github.com/rust-lang/crates.io-index" 3566source = "registry+https://github.com/rust-lang/crates.io-index"
3489checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3567checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -3502,6 +3580,12 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
3502 3580
3503[[package]] 3581[[package]]
3504name = "windows_x86_64_gnu" 3582name = "windows_x86_64_gnu"
3583version = "0.48.5"
3584source = "registry+https://github.com/rust-lang/crates.io-index"
3585checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
3586
3587[[package]]
3588name = "windows_x86_64_gnu"
3505version = "0.52.6" 3589version = "0.52.6"
3506source = "registry+https://github.com/rust-lang/crates.io-index" 3590source = "registry+https://github.com/rust-lang/crates.io-index"
3507checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3591checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -3520,6 +3604,12 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
3520 3604
3521[[package]] 3605[[package]]
3522name = "windows_x86_64_gnullvm" 3606name = "windows_x86_64_gnullvm"
3607version = "0.48.5"
3608source = "registry+https://github.com/rust-lang/crates.io-index"
3609checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
3610
3611[[package]]
3612name = "windows_x86_64_gnullvm"
3523version = "0.52.6" 3613version = "0.52.6"
3524source = "registry+https://github.com/rust-lang/crates.io-index" 3614source = "registry+https://github.com/rust-lang/crates.io-index"
3525checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3615checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -3538,6 +3628,12 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
3538 3628
3539[[package]] 3629[[package]]
3540name = "windows_x86_64_msvc" 3630name = "windows_x86_64_msvc"
3631version = "0.48.5"
3632source = "registry+https://github.com/rust-lang/crates.io-index"
3633checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
3634
3635[[package]]
3636name = "windows_x86_64_msvc"
3541version = "0.52.6" 3637version = "0.52.6"
3542source = "registry+https://github.com/rust-lang/crates.io-index" 3638source = "registry+https://github.com/rust-lang/crates.io-index"
3543checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3639checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -3702,7 +3798,7 @@ dependencies = [
3702 "hex", 3798 "hex",
3703 "libc", 3799 "libc",
3704 "ordered-stream", 3800 "ordered-stream",
3705 "rustix", 3801 "rustix 1.1.4",
3706 "serde", 3802 "serde",
3707 "serde_repr", 3803 "serde_repr",
3708 "tracing", 3804 "tracing",
diff --git a/Cargo.toml b/Cargo.toml
index 3482486..0fe9f43 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,6 +33,7 @@ libc = "0.2.186"
33ctrlc = "3.5.2" 33ctrlc = "3.5.2"
34rand = "0.10.1" 34rand = "0.10.1"
35notify-rust = "4.17.0" 35notify-rust = "4.17.0"
36terminal_size = "0.3"
36 37
37[dev-dependencies] 38[dev-dependencies]
38tempfile = "3.17" 39tempfile = "3.17"
diff --git a/src/cli.rs b/src/cli.rs
index 0da3078..bd81983 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -21,15 +21,12 @@ pub enum Commands {
21 Restart, 21 Restart,
22 /// Play a track, album, or artist 22 /// Play a track, album, or artist
23 Play { 23 Play {
24 /// Play immediately (add after current and skip to it) 24 /// Add after current track and skip to it
25 #[arg(short = 'i', long = "now", group = "position")] 25 #[arg(short = 'i', long = "now")]
26 now: bool, 26 now: bool,
27 /// Add to queue after current track (don't skip) 27 /// Add to queue after current track (don't skip)
28 #[arg(short = 'n', long = "next", group = "position")] 28 #[arg(short = 'n', long = "next")]
29 next: bool, 29 next: bool,
30 /// Add to end of queue (default)
31 #[arg(short = 'l', long = "last", group = "position")]
32 last: bool,
33 #[arg(value_name = "QUERY")] 30 #[arg(value_name = "QUERY")]
34 query: Option<String>, 31 query: Option<String>,
35 }, 32 },
@@ -126,7 +123,7 @@ impl Cli {
126 }; 123 };
127 crate::daemon::send_command(format!("play {} {}", pos, q))?; 124 crate::daemon::send_command(format!("play {} {}", pos, q))?;
128 } else { 125 } else {
129 crate::daemon::send_command("play".to_string())?; 126 crate::daemon::send_command("play last".to_string())?;
130 } 127 }
131 } 128 }
132 Commands::Pause => { 129 Commands::Pause => {
diff --git a/src/daemon.rs b/src/daemon.rs
index e88669a..f4a72b7 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -399,9 +399,9 @@ fn process_command(
399 format!("Queued next: {}\n", path_str) 399 format!("Queued next: {}\n", path_str)
400 } 400 }
401 _ => { 401 _ => {
402 let was_playing = audio.is_playing(); 402 let was_empty = q.is_empty();
403 q.add_track(path.clone()); 403 q.add_track(path.clone());
404 if !was_playing { 404 if was_empty && audio.is_playing() {
405 let track = Track::from_path(path.clone()); 405 let track = Track::from_path(path.clone());
406 audio.play(path); 406 audio.play(path);
407 notification::notify_track(&track); 407 notification::notify_track(&track);
@@ -452,7 +452,8 @@ fn process_command(
452 for file in &files { 452 for file in &files {
453 q.add_track(file.clone()); 453 q.add_track(file.clone());
454 } 454 }
455 if !audio.is_playing() 455 if was_empty
456 && audio.is_playing()
456 && let Some(track) = q.current() 457 && let Some(track) = q.current()
457 { 458 {
458 audio.play(track.path.clone()); 459 audio.play(track.path.clone());
@@ -519,8 +520,11 @@ fn process_command(
519 } 520 }
520 } 521 }
521 } 522 }
522 // Use pager if output is long (more than 24 lines) 523 // Use pager if queue is longer than terminal height
523 if output.lines().count() > 24 { 524 let terminal_rows = terminal_size::terminal_size()
525 .map(|(w, _)| w.0 as usize)
526 .unwrap_or(24);
527 if output.lines().count() > terminal_rows {
524 use std::process::{Command, Stdio}; 528 use std::process::{Command, Stdio};
525 let pager = Command::new("less").arg("-R").stdin(Stdio::piped()).spawn(); 529 let pager = Command::new("less").arg("-R").stdin(Stdio::piped()).spawn();
526 if let Ok(mut child) = pager { 530 if let Ok(mut child) = pager {
diff --git a/src/queue.rs b/src/queue.rs
index 9e8e1af..109b748 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -124,6 +124,7 @@ pub struct Queue {
124 position: usize, 124 position: usize,
125 shuffle: ShuffleMode, 125 shuffle: ShuffleMode,
126 shuffled_indices: Vec<usize>, 126 shuffled_indices: Vec<usize>,
127 original_tracks: Vec<Track>,
127} 128}
128 129
129impl Default for Queue { 130impl Default for Queue {
@@ -139,6 +140,7 @@ impl Queue {
139 position: 0, 140 position: 0,
140 shuffle: ShuffleMode::None, 141 shuffle: ShuffleMode::None,
141 shuffled_indices: Vec::new(), 142 shuffled_indices: Vec::new(),
143 original_tracks: Vec::new(),
142 } 144 }
143 } 145 }
144 146
@@ -162,8 +164,38 @@ impl Queue {
162 } 164 }
163 165
164 pub fn set_shuffle(&mut self, mode: ShuffleMode) { 166 pub fn set_shuffle(&mut self, mode: ShuffleMode) {
165 self.shuffle = mode; 167 match mode {
166 self.update_shuffle_indices(); 168 ShuffleMode::None => {
169 self.shuffle = ShuffleMode::None;
170 self.restore_from_original();
171 }
172 ShuffleMode::All => {
173 self.original_tracks = self.tracks.clone();
174 self.shuffle = ShuffleMode::All;
175 self.update_shuffle_indices();
176 }
177 ShuffleMode::Album => {
178 self.original_tracks = self.tracks.clone();
179 self.shuffle = ShuffleMode::Album;
180 self.update_shuffle_indices_album();
181 }
182 }
183 }
184
185 fn restore_from_original(&mut self) {
186 if self.original_tracks.is_empty() {
187 self.shuffled_indices.clear();
188 return;
189 }
190
191 let current_pos = self.position;
192 let played: Vec<Track> = self.tracks[..current_pos].to_vec();
193 let unplayed_original: Vec<Track> = self.original_tracks[current_pos..].to_vec();
194
195 self.tracks = played;
196 self.tracks.extend(unplayed_original);
197 self.position = current_pos;
198 self.shuffled_indices.clear();
167 } 199 }
168 200
169 fn update_shuffle_indices(&mut self) { 201 fn update_shuffle_indices(&mut self) {
@@ -180,6 +212,55 @@ impl Queue {
180 } 212 }
181 } 213 }
182 214
215 fn update_shuffle_indices_album(&mut self) {
216 let len = self.tracks.len();
217 self.shuffled_indices = (0..len).collect();
218
219 let mut rng = rand::rng();
220 let pos = self.position.min(len.saturating_sub(1));
221
222 if pos == 0 && len > 1 {
223 self.shuffle_album_range(0..len, &mut rng);
224 } else if pos + 1 < len {
225 self.shuffle_album_range(pos + 1..len, &mut rng);
226 }
227 }
228
229 fn shuffle_album_range(&mut self, range: std::ops::Range<usize>, rng: &mut impl rand::Rng) {
230 if range.start >= range.end {
231 return;
232 }
233
234 let mut album_ranges: Vec<(usize, usize)> = Vec::new();
235 let mut current_album: Option<&str> = None;
236 let mut range_start: Option<usize> = None;
237
238 for i in range.clone() {
239 let album = self.tracks.get(i).and_then(|t| t.album.as_deref());
240 match (current_album, range_start) {
241 (None, None) => {
242 current_album = album;
243 range_start = Some(i);
244 }
245 (Some(_), Some(start)) if album != current_album => {
246 album_ranges.push((start, i));
247 current_album = album;
248 range_start = Some(i);
249 }
250 _ => {}
251 }
252 }
253 if let Some(start) = range_start {
254 album_ranges.push((start, range.end));
255 }
256
257 for (start, end) in album_ranges {
258 if start + 1 < end {
259 self.shuffled_indices[start..end].shuffle(rng);
260 }
261 }
262 }
263
183 pub fn current(&self) -> Option<&Track> { 264 pub fn current(&self) -> Option<&Track> {
184 if self.tracks.is_empty() { 265 if self.tracks.is_empty() {
185 None 266 None