1use rand::seq::SliceRandom;
2use serde::{Deserialize, Serialize};
3
4use super::media::{MediaItem, MediaSource, QueueContext};
5
6#[derive(specta::Type, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
10#[serde(rename_all = "lowercase")]
11pub enum RepeatMode {
12 #[default]
13 Off,
14 All,
15 One,
16}
17
18#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
22pub struct QueueManager {
23 items: Vec<MediaItem>,
25 current_index: Option<usize>,
27 shuffle: bool,
29 repeat: RepeatMode,
31 shuffle_order: Vec<usize>,
33 history: Vec<usize>,
35 #[serde(default)]
38 context: QueueContext,
39}
40
41impl Default for QueueManager {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl QueueManager {
48 pub fn new() -> Self {
49 Self {
50 items: Vec::new(),
51 current_index: None,
52 shuffle: false,
53 repeat: RepeatMode::Off,
54 shuffle_order: Vec::new(),
55 history: Vec::new(),
56 context: QueueContext::Custom,
57 }
58 }
59
60 pub fn items(&self) -> &[MediaItem] {
62 &self.items
63 }
64
65 pub fn items_mut(&mut self) -> &mut [MediaItem] {
70 &mut self.items
71 }
72
73 pub fn current_index(&self) -> Option<usize> {
75 self.current_index
76 }
77
78 pub fn current(&self) -> Option<&MediaItem> {
80 self.current_index.and_then(|i| self.items.get(i))
81 }
82
83 pub fn is_shuffle(&self) -> bool {
85 self.shuffle
86 }
87
88 pub fn repeat_mode(&self) -> RepeatMode {
90 self.repeat
91 }
92
93 pub fn context(&self) -> &QueueContext {
95 &self.context
96 }
97
98 pub fn set_context(&mut self, context: QueueContext) {
100 self.context = context;
101 }
102
103 pub fn set_queue(&mut self, items: Vec<MediaItem>, start_index: usize) {
105 self.set_queue_with_context(items, start_index, QueueContext::Custom);
106 }
107
108 pub fn set_queue_with_context(
110 &mut self,
111 items: Vec<MediaItem>,
112 start_index: usize,
113 context: QueueContext,
114 ) {
115 let start_index = start_index.min(items.len().saturating_sub(1));
116
117 if self.shuffle && !items.is_empty() {
118 self.shuffle_order = self.generate_shuffle_order(items.len(), Some(start_index));
119 } else {
120 self.shuffle_order.clear();
121 }
122
123 self.items = items;
124 self.current_index = if self.items.is_empty() {
125 None
126 } else {
127 Some(start_index)
128 };
129 self.history.clear();
130 self.context = context;
131 }
132
133 pub fn clear(&mut self) {
139 self.items.clear();
140 self.current_index = None;
141 self.history.clear();
142 self.shuffle_order.clear();
143 }
144
145 pub fn add(&mut self, items: Vec<MediaItem>, position: AddPosition) {
147 if items.is_empty() {
148 return;
149 }
150
151 let insert_index = match position {
152 AddPosition::Next => self
153 .current_index
154 .map(|i| i + 1)
155 .unwrap_or(self.items.len()),
156 AddPosition::End => self.items.len(),
157 };
158
159 for (i, item) in items.into_iter().enumerate() {
161 self.items.insert(insert_index + i, item);
162 }
163
164 if let Some(current) = self.current_index {
166 if insert_index <= current {
167 self.current_index = Some(current + 1);
168 }
169 }
170
171 if self.shuffle {
173 self.shuffle_order = self.generate_shuffle_order(self.items.len(), self.current_index);
174 }
175 }
176
177 pub fn remove(&mut self, index: usize) -> Option<MediaItem> {
179 if index >= self.items.len() {
180 return None;
181 }
182
183 let removed = self.items.remove(index);
184
185 if let Some(current) = self.current_index {
187 if index < current {
188 self.current_index = Some(current - 1);
189 } else if index == current {
190 self.current_index = if self.items.is_empty() {
191 None
192 } else if index >= self.items.len() {
193 Some(self.items.len() - 1)
194 } else {
195 Some(index)
196 };
197 }
198 }
199
200 if self.shuffle {
202 self.shuffle_order = self
203 .shuffle_order
204 .iter()
205 .filter(|&&i| i != index)
206 .map(|&i| if i > index { i - 1 } else { i })
207 .collect();
208 }
209
210 Some(removed)
211 }
212
213 pub fn next(&mut self) -> Option<&MediaItem> {
215 if self.items.is_empty() {
216 return None;
217 }
218
219 let current = self.current_index?;
220
221 let next_index = if self.repeat == RepeatMode::One {
222 current
224 } else if self.shuffle {
225 let pos = self.shuffle_order.iter().position(|&i| i == current)?;
227 if pos + 1 < self.shuffle_order.len() {
228 self.shuffle_order[pos + 1]
229 } else if self.repeat == RepeatMode::All {
230 self.shuffle_order[0]
232 } else {
233 log::debug!("[Queue] next() at end of shuffle order, no next track");
234 return None;
235 }
236 } else {
237 if current + 1 < self.items.len() {
239 current + 1
240 } else if self.repeat == RepeatMode::All {
241 0
242 } else {
243 log::debug!(
244 "[Queue] next() at end of queue (index {}), no next track",
245 current
246 );
247 return None;
248 }
249 };
250
251 if next_index != current {
253 self.history.push(current);
254 log::debug!("[Queue] next() moving: {} -> {}", current, next_index);
255 } else {
256 log::debug!("[Queue] next() repeat one, staying at index {}", current);
257 }
258
259 self.current_index = Some(next_index);
260 self.items.get(next_index)
261 }
262
263 pub fn previous(&mut self) -> Option<&MediaItem> {
265 if self.items.is_empty() {
266 return None;
267 }
268
269 let current = self.current_index?;
270
271 if let Some(prev) = self.history.pop() {
274 if prev >= self.items.len() {
276 log::warn!(
277 "[Queue] Invalid history entry {} (queue has {} items), clearing history",
278 prev,
279 self.items.len()
280 );
281 self.history.clear();
282 return None;
283 }
284
285 if !self.shuffle && prev >= current {
288 log::warn!("[Queue] Suspicious history: going from index {} to {} (non-shuffle mode), clearing history",
289 current, prev);
290 self.history.clear();
291 return None;
292 }
293
294 log::debug!("[Queue] previous() using history: {} -> {}", current, prev);
295 self.current_index = Some(prev);
296 return self.items.get(prev);
297 }
298
299 log::debug!("[Queue] previous() no history, current={}", current);
300
301 let prev_index = if self.shuffle {
302 let pos = self.shuffle_order.iter().position(|&i| i == current)?;
304 if pos > 0 {
305 self.shuffle_order[pos - 1]
306 } else {
307 log::debug!("[Queue] previous() at start of shuffle order, staying at current");
308 return None;
309 }
310 } else {
311 if current > 0 {
313 current - 1
314 } else {
315 log::debug!("[Queue] previous() at index 0, staying at current");
316 return None;
317 }
318 };
319
320 log::debug!("[Queue] previous() moving: {} -> {}", current, prev_index);
321 self.current_index = Some(prev_index);
322 self.items.get(prev_index)
323 }
324
325 pub fn skip_to(&mut self, index: usize) -> Option<&MediaItem> {
327 if index >= self.items.len() {
328 return None;
329 }
330
331 if let Some(current) = self.current_index {
332 self.history.push(current);
333 }
334
335 self.current_index = Some(index);
336 self.items.get(index)
337 }
338
339 pub fn toggle_shuffle(&mut self) {
341 self.shuffle = !self.shuffle;
342
343 if self.shuffle && !self.items.is_empty() {
344 self.shuffle_order = self.generate_shuffle_order(self.items.len(), self.current_index);
345 } else {
346 self.shuffle_order.clear();
347 }
348 }
349
350 pub fn cycle_repeat(&mut self) {
352 self.repeat = match self.repeat {
353 RepeatMode::Off => RepeatMode::All,
354 RepeatMode::All => RepeatMode::One,
355 RepeatMode::One => RepeatMode::Off,
356 };
357 }
358
359 #[cfg(test)]
361 pub fn set_repeat(&mut self, mode: RepeatMode) {
362 self.repeat = mode;
363 }
364
365 pub fn has_next(&self) -> bool {
367 if self.items.is_empty() {
368 return false;
369 }
370
371 match self.current_index {
372 None => false,
373 Some(current) => {
374 if self.repeat == RepeatMode::All || self.repeat == RepeatMode::One {
375 true
376 } else if self.shuffle {
377 let pos = self.shuffle_order.iter().position(|&i| i == current);
378 pos.map(|p| p + 1 < self.shuffle_order.len())
379 .unwrap_or(false)
380 } else {
381 current + 1 < self.items.len()
382 }
383 }
384 }
385 }
386
387 pub fn has_previous(&self) -> bool {
389 !self.history.is_empty() || {
390 match self.current_index {
391 None => false,
392 Some(current) => {
393 if self.shuffle {
394 let pos = self.shuffle_order.iter().position(|&i| i == current);
395 pos.map(|p| p > 0).unwrap_or(false)
396 } else {
397 current > 0
398 }
399 }
400 }
401 }
402 }
403
404 pub fn get_upcoming(&self, count: usize) -> Vec<&MediaItem> {
407 if self.items.is_empty() || count == 0 {
408 return Vec::new();
409 }
410
411 let current = match self.current_index {
412 Some(idx) => idx,
413 None => return Vec::new(),
414 };
415
416 let mut upcoming = Vec::with_capacity(count);
417
418 if self.shuffle {
419 if let Some(pos) = self.shuffle_order.iter().position(|&i| i == current) {
421 for i in 1..=count {
422 let next_pos = pos + i;
423 if next_pos < self.shuffle_order.len() {
424 if let Some(item) = self.items.get(self.shuffle_order[next_pos]) {
425 upcoming.push(item);
426 }
427 } else if self.repeat == RepeatMode::All {
428 let wrapped_pos = (next_pos) % self.shuffle_order.len();
430 if let Some(item) = self.items.get(self.shuffle_order[wrapped_pos]) {
431 upcoming.push(item);
432 }
433 }
434 }
435 }
436 } else {
437 for i in 1..=count {
439 let next_idx = current + i;
440 if next_idx < self.items.len() {
441 upcoming.push(&self.items[next_idx]);
442 } else if self.repeat == RepeatMode::All {
443 let wrapped_idx = next_idx % self.items.len();
445 upcoming.push(&self.items[wrapped_idx]);
446 }
447 }
448 }
449
450 upcoming
451 }
452
453 pub fn move_item(&mut self, from_index: usize, to_index: usize) -> bool {
455 if from_index >= self.items.len() || to_index >= self.items.len() || from_index == to_index
456 {
457 return false;
458 }
459
460 let item = self.items.remove(from_index);
462 self.items.insert(to_index, item);
463
464 if let Some(current) = self.current_index {
466 if current == from_index {
467 self.current_index = Some(to_index);
469 } else if from_index < current && to_index >= current {
470 self.current_index = Some(current - 1);
472 } else if from_index > current && to_index <= current {
473 self.current_index = Some(current + 1);
475 }
476 }
477
478 if self.shuffle && !self.shuffle_order.is_empty() {
480 self.shuffle_order = self.generate_shuffle_order(self.items.len(), self.current_index);
482 }
483
484 true
485 }
486
487 pub fn update_current_stream_url(&mut self, new_url: String) -> bool {
490 if let Some(current_index) = self.current_index {
491 if let Some(item) = self.items.get_mut(current_index) {
492 if let MediaSource::Remote {
494 jellyfin_item_id, ..
495 } = &item.source
496 {
497 item.source = MediaSource::Remote {
498 stream_url: new_url,
499 jellyfin_item_id: jellyfin_item_id.clone(),
500 };
501 return true;
502 }
503 }
504 }
505 false
506 }
507
508 fn generate_shuffle_order(&self, length: usize, start_index: Option<usize>) -> Vec<usize> {
510 let mut indices: Vec<usize> = (0..length).collect();
511 let mut rng = rand::thread_rng();
512 indices.shuffle(&mut rng);
513
514 if let Some(start) = start_index {
516 if let Some(pos) = indices.iter().position(|&i| i == start) {
517 indices.remove(pos);
518 indices.insert(0, start);
519 }
520 }
521
522 indices
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub enum AddPosition {
529 Next,
531 End,
533}
534
535#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::player::media::{MediaSource, MediaType};
540
541 fn create_test_items(count: usize) -> Vec<MediaItem> {
542 (0..count)
543 .map(|i| MediaItem {
544 id: format!("item_{}", i),
545 title: format!("Track {}", i + 1),
546 name: Some(format!("Track {}", i + 1)),
547 artist: Some("Artist".to_string()),
548 album: None,
549 album_name: None,
550 album_id: None,
551 artist_items: None,
552 artists: Some(vec!["Artist".to_string()]),
553 primary_image_tag: None,
554 image_id: None,
555 item_type: Some("Audio".to_string()),
556 playlist_id: None,
557 duration: Some(180.0),
558 artwork_url: None,
559 media_type: MediaType::Audio,
560 source: MediaSource::DirectUrl {
561 url: format!("http://example.com/track_{}.mp3", i),
562 },
563 video_codec: None,
564 needs_transcoding: false,
565 video_width: None,
566 video_height: None,
567 subtitles: vec![],
568 series_id: None,
569 server_id: None,
570 })
571 .collect()
572 }
573
574 #[test]
579 fn test_set_queue() {
580 let mut queue = QueueManager::new();
581 let items = create_test_items(5);
582
583 queue.set_queue(items.clone(), 0);
584
585 assert_eq!(queue.items().len(), 5);
586 assert_eq!(queue.current_index(), Some(0));
587 assert_eq!(queue.current().unwrap().id, "item_0");
588 }
589
590 #[test]
593 fn test_clear() {
594 let mut queue = QueueManager::new();
595 queue.set_queue(create_test_items(3), 1);
596 assert_eq!(queue.current_index(), Some(1));
597
598 queue.clear();
599
600 assert_eq!(queue.items().len(), 0);
601 assert_eq!(queue.current_index(), None);
602 assert!(queue.current().is_none());
603 }
604
605 #[test]
610 fn test_next() {
611 let mut queue = QueueManager::new();
612 queue.set_queue(create_test_items(3), 0);
613
614 assert_eq!(queue.current().unwrap().id, "item_0");
615
616 queue.next();
617 assert_eq!(queue.current().unwrap().id, "item_1");
618
619 queue.next();
620 assert_eq!(queue.current().unwrap().id, "item_2");
621
622 assert!(queue.next().is_none());
624 }
625
626 #[test]
631 fn test_repeat_all() {
632 let mut queue = QueueManager::new();
633 queue.set_queue(create_test_items(2), 0);
634 queue.set_repeat(RepeatMode::All);
635
636 queue.next(); let next = queue.next(); assert!(next.is_some());
640 assert_eq!(queue.current().unwrap().id, "item_0");
641 }
642
643 #[test]
648 fn test_previous() {
649 let mut queue = QueueManager::new();
650 queue.set_queue(create_test_items(3), 2);
651
652 queue.previous();
653 assert_eq!(queue.current_index(), Some(1));
654 }
655
656 #[test]
661 fn test_get_upcoming() {
662 let mut queue = QueueManager::new();
663 queue.set_queue(create_test_items(5), 0);
664
665 let upcoming = queue.get_upcoming(3);
667 assert_eq!(upcoming.len(), 3);
668 assert_eq!(upcoming[0].id, "item_1");
669 assert_eq!(upcoming[1].id, "item_2");
670 assert_eq!(upcoming[2].id, "item_3");
671
672 queue.next();
674 queue.next();
675 let upcoming = queue.get_upcoming(3);
676 assert_eq!(upcoming.len(), 2); assert_eq!(upcoming[0].id, "item_3");
678 assert_eq!(upcoming[1].id, "item_4");
679 }
680
681 #[test]
686 fn test_get_upcoming_with_repeat() {
687 let mut queue = QueueManager::new();
688 queue.set_queue(create_test_items(3), 1);
689 queue.set_repeat(RepeatMode::All);
690
691 let upcoming = queue.get_upcoming(4);
693 assert_eq!(upcoming.len(), 4);
694 assert_eq!(upcoming[0].id, "item_2"); assert_eq!(upcoming[1].id, "item_0"); assert_eq!(upcoming[2].id, "item_1"); assert_eq!(upcoming[3].id, "item_2"); }
699
700 #[test]
704 fn test_get_upcoming_empty() {
705 let queue = QueueManager::new();
706 let upcoming = queue.get_upcoming(3);
707 assert!(upcoming.is_empty());
708 }
709
710 #[test]
715 fn test_next_no_repeat_reaches_end() {
716 let mut queue = QueueManager::new();
717 queue.set_queue(create_test_items(3), 0);
718 queue.set_repeat(RepeatMode::Off);
719
720 assert!(queue.has_next());
722 assert_eq!(queue.current_index(), Some(0));
723
724 queue.next();
726 assert!(queue.has_next());
727 assert_eq!(queue.current_index(), Some(1));
728
729 queue.next();
731 assert!(!queue.has_next()); assert_eq!(queue.current_index(), Some(2));
733
734 let result = queue.next();
736 assert!(result.is_none());
737 assert_eq!(queue.current_index(), Some(2)); }
739
740 #[test]
745 fn test_next_repeat_all_wraps() {
746 let mut queue = QueueManager::new();
747 queue.set_queue(create_test_items(3), 0);
748 queue.set_repeat(RepeatMode::All);
749
750 assert!(queue.has_next());
752
753 queue.next(); assert!(queue.has_next());
756 queue.next(); assert!(queue.has_next());
758
759 let result = queue.next();
761 assert!(result.is_some());
762 assert_eq!(queue.current().unwrap().id, "item_0");
763 assert!(queue.has_next()); }
765
766 #[test]
771 fn test_next_repeat_one_stays() {
772 let mut queue = QueueManager::new();
773 queue.set_queue(create_test_items(3), 1);
774 queue.set_repeat(RepeatMode::One);
775
776 assert!(queue.has_next());
778 assert_eq!(queue.current_index(), Some(1));
779
780 for _ in 0..5 {
782 let result = queue.next();
783 assert!(result.is_some());
784 assert_eq!(queue.current().unwrap().id, "item_1");
785 assert_eq!(queue.current_index(), Some(1));
786 assert!(queue.has_next());
787 }
788 }
789
790 #[test]
795 fn test_next_shuffle_follows_order() {
796 let mut queue = QueueManager::new();
797 queue.set_queue(create_test_items(4), 0);
798 queue.toggle_shuffle(); let shuffle_order = queue.shuffle_order.clone();
802 assert_eq!(shuffle_order.len(), 4);
803
804 let first_shuffled_index = shuffle_order[0];
806 assert_eq!(queue.current_index(), Some(first_shuffled_index));
807
808 for &expected_index in &shuffle_order[1..] {
810 assert!(queue.has_next());
811 let result = queue.next();
812 assert!(result.is_some());
813 assert_eq!(queue.current_index(), Some(expected_index));
814 }
815
816 assert!(!queue.has_next());
818 let result = queue.next();
819 assert!(result.is_none());
820 }
821
822 #[test]
827 fn test_next_shuffle_with_repeat_all() {
828 let mut queue = QueueManager::new();
829 queue.set_queue(create_test_items(3), 0);
830 queue.toggle_shuffle(); queue.set_repeat(RepeatMode::All);
832
833 let shuffle_order = queue.shuffle_order.clone();
834
835 for _ in 1..shuffle_order.len() {
837 queue.next();
838 }
839
840 assert!(queue.has_next());
842 let result = queue.next();
843 assert!(result.is_some());
844 assert_eq!(queue.current_index(), Some(shuffle_order[0]));
845 }
846
847 #[test]
852 fn test_has_next_accuracy() {
853 let queue = QueueManager::new();
855 assert!(!queue.has_next());
856
857 let mut queue = QueueManager::new();
859 queue.set_queue(create_test_items(2), 1); queue.set_repeat(RepeatMode::Off);
861 assert!(!queue.has_next());
862
863 let mut queue = QueueManager::new();
865 queue.set_queue(create_test_items(2), 1); queue.set_repeat(RepeatMode::All);
867 assert!(queue.has_next()); let mut queue = QueueManager::new();
871 queue.set_queue(create_test_items(1), 0);
872 queue.set_repeat(RepeatMode::One);
873 assert!(queue.has_next()); let mut queue = QueueManager::new();
877 queue.set_queue(create_test_items(3), 1); queue.set_repeat(RepeatMode::Off);
879 assert!(queue.has_next()); let mut queue = QueueManager::new();
883 queue.set_queue(create_test_items(3), 0);
884 queue.toggle_shuffle(); queue.set_repeat(RepeatMode::Off);
886 let shuffle_len = queue.shuffle_order.len();
888 for _ in 1..shuffle_len {
889 queue.next();
890 }
891 assert!(!queue.has_next());
892 }
893
894 #[test]
898 fn test_has_next_empty_queue() {
899 let queue = QueueManager::new();
900 assert!(!queue.has_next());
901 assert_eq!(queue.current_index(), None);
902 }
903
904 #[test]
908 fn test_play_specific_track_from_album() {
909 let mut queue = QueueManager::new();
910 let items = create_test_items(14); queue.set_queue(items.clone(), 0);
914 assert_eq!(queue.current_index(), Some(0));
915 assert_eq!(queue.current().unwrap().id, "item_0");
916
917 queue.set_queue(items.clone(), 3);
919 assert_eq!(queue.current_index(), Some(3));
920 assert_eq!(queue.current().unwrap().id, "item_3");
921
922 queue.set_queue(items, 13);
924 assert_eq!(queue.current_index(), Some(13));
925 assert_eq!(queue.current().unwrap().id, "item_13");
926 }
927
928 #[test]
930 fn test_next_previous_roundtrip() {
931 let mut queue = QueueManager::new();
932 queue.set_queue(create_test_items(5), 2); assert_eq!(queue.current_index(), Some(2));
935
936 queue.next();
938 assert_eq!(queue.current_index(), Some(3));
939
940 queue.previous();
942 assert_eq!(queue.current_index(), Some(2));
943 assert_eq!(queue.current().unwrap().id, "item_2");
944 }
945
946 #[test]
948 fn test_previous_at_first_track_stays() {
949 let mut queue = QueueManager::new();
950 queue.set_queue(create_test_items(5), 0); assert_eq!(queue.current_index(), Some(0));
953
954 let result = queue.previous();
956 assert!(result.is_none());
957 assert_eq!(queue.current_index(), Some(0));
958 assert_eq!(queue.current().unwrap().id, "item_0");
959 }
960
961 #[test]
963 fn test_next_at_last_track_stays() {
964 let mut queue = QueueManager::new();
965 queue.set_queue(create_test_items(5), 4); assert_eq!(queue.current_index(), Some(4));
968
969 let result = queue.next();
971 assert!(result.is_none());
972 assert_eq!(queue.current_index(), Some(4));
973 assert_eq!(queue.current().unwrap().id, "item_4");
974 }
975
976 #[test]
978 fn test_history_validation_prevents_wraparound() {
979 let mut queue = QueueManager::new();
980 queue.set_queue(create_test_items(5), 0);
981
982 queue.history.push(4);
985
986 let result = queue.previous();
988 assert!(result.is_none()); assert_eq!(queue.current_index(), Some(0)); assert!(queue.history.is_empty()); }
992
993 #[test]
995 fn test_multiple_next_builds_history() {
996 let mut queue = QueueManager::new();
997 queue.set_queue(create_test_items(5), 0);
998
999 queue.next(); queue.next(); queue.next(); assert_eq!(queue.current_index(), Some(3));
1005
1006 queue.previous();
1008 assert_eq!(queue.current_index(), Some(2));
1009
1010 queue.previous();
1011 assert_eq!(queue.current_index(), Some(1));
1012
1013 queue.previous();
1014 assert_eq!(queue.current_index(), Some(0));
1015 }
1016}