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 transport: None,
546 id: format!("item_{}", i),
547 title: format!("Track {}", i + 1),
548 name: Some(format!("Track {}", i + 1)),
549 artist: Some("Artist".to_string()),
550 album: None,
551 album_name: None,
552 album_id: None,
553 artist_items: None,
554 artists: Some(vec!["Artist".to_string()]),
555 primary_image_tag: None,
556 image_id: None,
557 item_type: Some("Audio".to_string()),
558 playlist_id: None,
559 duration: Some(180.0),
560 artwork_url: None,
561 media_type: MediaType::Audio,
562 source: MediaSource::DirectUrl {
563 url: format!("http://example.com/track_{}.mp3", i),
564 },
565 video_codec: None,
566 needs_transcoding: false,
567 video_width: None,
568 video_height: None,
569 subtitles: vec![],
570 series_id: None,
571 server_id: None,
572 })
573 .collect()
574 }
575
576 #[test]
581 fn test_set_queue() {
582 let mut queue = QueueManager::new();
583 let items = create_test_items(5);
584
585 queue.set_queue(items.clone(), 0);
586
587 assert_eq!(queue.items().len(), 5);
588 assert_eq!(queue.current_index(), Some(0));
589 assert_eq!(queue.current().unwrap().id, "item_0");
590 }
591
592 #[test]
595 fn test_clear() {
596 let mut queue = QueueManager::new();
597 queue.set_queue(create_test_items(3), 1);
598 assert_eq!(queue.current_index(), Some(1));
599
600 queue.clear();
601
602 assert_eq!(queue.items().len(), 0);
603 assert_eq!(queue.current_index(), None);
604 assert!(queue.current().is_none());
605 }
606
607 #[test]
612 fn test_next() {
613 let mut queue = QueueManager::new();
614 queue.set_queue(create_test_items(3), 0);
615
616 assert_eq!(queue.current().unwrap().id, "item_0");
617
618 queue.next();
619 assert_eq!(queue.current().unwrap().id, "item_1");
620
621 queue.next();
622 assert_eq!(queue.current().unwrap().id, "item_2");
623
624 assert!(queue.next().is_none());
626 }
627
628 #[test]
633 fn test_repeat_all() {
634 let mut queue = QueueManager::new();
635 queue.set_queue(create_test_items(2), 0);
636 queue.set_repeat(RepeatMode::All);
637
638 queue.next(); let next = queue.next(); assert!(next.is_some());
642 assert_eq!(queue.current().unwrap().id, "item_0");
643 }
644
645 #[test]
650 fn test_previous() {
651 let mut queue = QueueManager::new();
652 queue.set_queue(create_test_items(3), 2);
653
654 queue.previous();
655 assert_eq!(queue.current_index(), Some(1));
656 }
657
658 #[test]
663 fn test_get_upcoming() {
664 let mut queue = QueueManager::new();
665 queue.set_queue(create_test_items(5), 0);
666
667 let upcoming = queue.get_upcoming(3);
669 assert_eq!(upcoming.len(), 3);
670 assert_eq!(upcoming[0].id, "item_1");
671 assert_eq!(upcoming[1].id, "item_2");
672 assert_eq!(upcoming[2].id, "item_3");
673
674 queue.next();
676 queue.next();
677 let upcoming = queue.get_upcoming(3);
678 assert_eq!(upcoming.len(), 2); assert_eq!(upcoming[0].id, "item_3");
680 assert_eq!(upcoming[1].id, "item_4");
681 }
682
683 #[test]
688 fn test_get_upcoming_with_repeat() {
689 let mut queue = QueueManager::new();
690 queue.set_queue(create_test_items(3), 1);
691 queue.set_repeat(RepeatMode::All);
692
693 let upcoming = queue.get_upcoming(4);
695 assert_eq!(upcoming.len(), 4);
696 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"); }
701
702 #[test]
706 fn test_get_upcoming_empty() {
707 let queue = QueueManager::new();
708 let upcoming = queue.get_upcoming(3);
709 assert!(upcoming.is_empty());
710 }
711
712 #[test]
717 fn test_next_no_repeat_reaches_end() {
718 let mut queue = QueueManager::new();
719 queue.set_queue(create_test_items(3), 0);
720 queue.set_repeat(RepeatMode::Off);
721
722 assert!(queue.has_next());
724 assert_eq!(queue.current_index(), Some(0));
725
726 queue.next();
728 assert!(queue.has_next());
729 assert_eq!(queue.current_index(), Some(1));
730
731 queue.next();
733 assert!(!queue.has_next()); assert_eq!(queue.current_index(), Some(2));
735
736 let result = queue.next();
738 assert!(result.is_none());
739 assert_eq!(queue.current_index(), Some(2)); }
741
742 #[test]
747 fn test_next_repeat_all_wraps() {
748 let mut queue = QueueManager::new();
749 queue.set_queue(create_test_items(3), 0);
750 queue.set_repeat(RepeatMode::All);
751
752 assert!(queue.has_next());
754
755 queue.next(); assert!(queue.has_next());
758 queue.next(); assert!(queue.has_next());
760
761 let result = queue.next();
763 assert!(result.is_some());
764 assert_eq!(queue.current().unwrap().id, "item_0");
765 assert!(queue.has_next()); }
767
768 #[test]
773 fn test_next_repeat_one_stays() {
774 let mut queue = QueueManager::new();
775 queue.set_queue(create_test_items(3), 1);
776 queue.set_repeat(RepeatMode::One);
777
778 assert!(queue.has_next());
780 assert_eq!(queue.current_index(), Some(1));
781
782 for _ in 0..5 {
784 let result = queue.next();
785 assert!(result.is_some());
786 assert_eq!(queue.current().unwrap().id, "item_1");
787 assert_eq!(queue.current_index(), Some(1));
788 assert!(queue.has_next());
789 }
790 }
791
792 #[test]
797 fn test_next_shuffle_follows_order() {
798 let mut queue = QueueManager::new();
799 queue.set_queue(create_test_items(4), 0);
800 queue.toggle_shuffle(); let shuffle_order = queue.shuffle_order.clone();
804 assert_eq!(shuffle_order.len(), 4);
805
806 let first_shuffled_index = shuffle_order[0];
808 assert_eq!(queue.current_index(), Some(first_shuffled_index));
809
810 for &expected_index in &shuffle_order[1..] {
812 assert!(queue.has_next());
813 let result = queue.next();
814 assert!(result.is_some());
815 assert_eq!(queue.current_index(), Some(expected_index));
816 }
817
818 assert!(!queue.has_next());
820 let result = queue.next();
821 assert!(result.is_none());
822 }
823
824 #[test]
829 fn test_next_shuffle_with_repeat_all() {
830 let mut queue = QueueManager::new();
831 queue.set_queue(create_test_items(3), 0);
832 queue.toggle_shuffle(); queue.set_repeat(RepeatMode::All);
834
835 let shuffle_order = queue.shuffle_order.clone();
836
837 for _ in 1..shuffle_order.len() {
839 queue.next();
840 }
841
842 assert!(queue.has_next());
844 let result = queue.next();
845 assert!(result.is_some());
846 assert_eq!(queue.current_index(), Some(shuffle_order[0]));
847 }
848
849 #[test]
854 fn test_has_next_accuracy() {
855 let queue = QueueManager::new();
857 assert!(!queue.has_next());
858
859 let mut queue = QueueManager::new();
861 queue.set_queue(create_test_items(2), 1); queue.set_repeat(RepeatMode::Off);
863 assert!(!queue.has_next());
864
865 let mut queue = QueueManager::new();
867 queue.set_queue(create_test_items(2), 1); queue.set_repeat(RepeatMode::All);
869 assert!(queue.has_next()); let mut queue = QueueManager::new();
873 queue.set_queue(create_test_items(1), 0);
874 queue.set_repeat(RepeatMode::One);
875 assert!(queue.has_next()); let mut queue = QueueManager::new();
879 queue.set_queue(create_test_items(3), 1); queue.set_repeat(RepeatMode::Off);
881 assert!(queue.has_next()); let mut queue = QueueManager::new();
885 queue.set_queue(create_test_items(3), 0);
886 queue.toggle_shuffle(); queue.set_repeat(RepeatMode::Off);
888 let shuffle_len = queue.shuffle_order.len();
890 for _ in 1..shuffle_len {
891 queue.next();
892 }
893 assert!(!queue.has_next());
894 }
895
896 #[test]
900 fn test_has_next_empty_queue() {
901 let queue = QueueManager::new();
902 assert!(!queue.has_next());
903 assert_eq!(queue.current_index(), None);
904 }
905
906 #[test]
910 fn test_play_specific_track_from_album() {
911 let mut queue = QueueManager::new();
912 let items = create_test_items(14); queue.set_queue(items.clone(), 0);
916 assert_eq!(queue.current_index(), Some(0));
917 assert_eq!(queue.current().unwrap().id, "item_0");
918
919 queue.set_queue(items.clone(), 3);
921 assert_eq!(queue.current_index(), Some(3));
922 assert_eq!(queue.current().unwrap().id, "item_3");
923
924 queue.set_queue(items, 13);
926 assert_eq!(queue.current_index(), Some(13));
927 assert_eq!(queue.current().unwrap().id, "item_13");
928 }
929
930 #[test]
932 fn test_next_previous_roundtrip() {
933 let mut queue = QueueManager::new();
934 queue.set_queue(create_test_items(5), 2); assert_eq!(queue.current_index(), Some(2));
937
938 queue.next();
940 assert_eq!(queue.current_index(), Some(3));
941
942 queue.previous();
944 assert_eq!(queue.current_index(), Some(2));
945 assert_eq!(queue.current().unwrap().id, "item_2");
946 }
947
948 #[test]
950 fn test_previous_at_first_track_stays() {
951 let mut queue = QueueManager::new();
952 queue.set_queue(create_test_items(5), 0); assert_eq!(queue.current_index(), Some(0));
955
956 let result = queue.previous();
958 assert!(result.is_none());
959 assert_eq!(queue.current_index(), Some(0));
960 assert_eq!(queue.current().unwrap().id, "item_0");
961 }
962
963 #[test]
965 fn test_next_at_last_track_stays() {
966 let mut queue = QueueManager::new();
967 queue.set_queue(create_test_items(5), 4); assert_eq!(queue.current_index(), Some(4));
970
971 let result = queue.next();
973 assert!(result.is_none());
974 assert_eq!(queue.current_index(), Some(4));
975 assert_eq!(queue.current().unwrap().id, "item_4");
976 }
977
978 #[test]
980 fn test_history_validation_prevents_wraparound() {
981 let mut queue = QueueManager::new();
982 queue.set_queue(create_test_items(5), 0);
983
984 queue.history.push(4);
987
988 let result = queue.previous();
990 assert!(result.is_none()); assert_eq!(queue.current_index(), Some(0)); assert!(queue.history.is_empty()); }
994
995 #[test]
997 fn test_multiple_next_builds_history() {
998 let mut queue = QueueManager::new();
999 queue.set_queue(create_test_items(5), 0);
1000
1001 queue.next(); queue.next(); queue.next(); assert_eq!(queue.current_index(), Some(3));
1007
1008 queue.previous();
1010 assert_eq!(queue.current_index(), Some(2));
1011
1012 queue.previous();
1013 assert_eq!(queue.current_index(), Some(1));
1014
1015 queue.previous();
1016 assert_eq!(queue.current_index(), Some(0));
1017 }
1018}