/
magnusroot
/
nm
Обзор
Документация
Войти
/
magnusroot
/
nm
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
crates/player/src/queue.rs
206 строк
7 KB
Magnus Root
First version
13 июл 2026, 09:24
13 июл 2026, 09:24
50f8b8c
Код
Авторство
О чём код?
use rand::seq::SliceRandom; use rand::thread_rng; use crate::types::{QueueItem, RepeatMode}; #[derive(Debug, Default)] pub struct Queue { /// Оригинальный порядок (как задан пользователем/альбомом/плейлистом/cue-листом). original: Vec<QueueItem>, /// Порядок воспроизведения — либо совпадает с original, либо перемешан. order: Vec<usize>, current: Option<usize>, // индекс в order pub shuffle: bool, pub repeat: RepeatMode, } impl Queue { pub fn set_tracks(&mut self, tracks: Vec<QueueItem>, start_at: usize) { self.original = tracks; self.rebuild_order(Some(start_at)); } pub fn push(&mut self, item: QueueItem) { self.original.push(item); let new_idx = self.original.len() - 1; self.order.push(new_idx); } fn rebuild_order(&mut self, keep_track_original_idx: Option<usize>) { self.order = (0..self.original.len()).collect(); if self.shuffle { self.order.shuffle(&mut thread_rng()); } self.current = keep_track_original_idx.and_then(|orig_idx| { self.order.iter().position(|&i| i == orig_idx) }); } pub fn set_shuffle(&mut self, shuffle: bool) { if self.shuffle == shuffle { return; } self.shuffle = shuffle; let current_orig = self.current_original_index(); self.rebuild_order(current_orig); } pub fn cycle_repeat(&mut self) { self.repeat = self.repeat.cycle(); } fn current_original_index(&self) -> Option<usize> { self.current.and_then(|c| self.order.get(c).copied()) } /// Индекс в ИСХОДНОМ порядке (как он был передан в set_tracks), а не в порядке /// воспроизведения — то есть именно тот индекс, который знает вызывающий код. pub fn current_index(&self) -> Option<usize> { self.current_original_index() } pub fn current_item(&self) -> Option<&QueueItem> { self.current_original_index().map(|i| &self.original[i]) } /// Продвигает очередь вперёд. Возвращает false, если очередь закончилась /// (и повтор выключен) — в этом случае воспроизведение должно остановиться. pub fn advance(&mut self) -> bool { if self.original.is_empty() { return false; } match self.repeat { RepeatMode::Track => true, // остаёмся на месте, просто перезапускаем трек _ => { let next = self.current.map(|c| c + 1).unwrap_or(0); if next < self.order.len() { self.current = Some(next); true } else if self.repeat == RepeatMode::All { self.current = Some(0); true } else { self.current = None; false } } } } pub fn previous(&mut self) -> bool { if self.original.is_empty() { return false; } match self.current { Some(0) | None => { if self.repeat == RepeatMode::All { self.current = Some(self.order.len() - 1); true } else { false } } Some(c) => { self.current = Some(c - 1); true } } } } #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; fn p(s: &str) -> QueueItem { QueueItem::whole_file(PathBuf::from(s)) } #[test] fn advance_without_repeat_stops_at_end() { let mut q = Queue::default(); q.set_tracks(vec![p("a"), p("b")], 0); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("a"))); assert!(q.advance()); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("b"))); assert!(!q.advance()); } #[test] fn repeat_all_wraps_around() { let mut q = Queue::default(); q.repeat = RepeatMode::All; q.set_tracks(vec![p("a"), p("b")], 0); q.advance(); assert!(q.advance()); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("a"))); } #[test] fn repeat_track_stays_put() { let mut q = Queue::default(); q.repeat = RepeatMode::Track; q.set_tracks(vec![p("a"), p("b")], 0); assert!(q.advance()); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("a"))); } #[test] fn shuffle_preserves_current_track() { let mut q = Queue::default(); q.set_tracks(vec![p("a"), p("b"), p("c"), p("d")], 2); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("c"))); q.set_shuffle(true); assert_eq!(q.current_item().map(|i| &i.path), Some(&PathBuf::from("c"))); } #[test] fn cue_virtual_tracks_share_path_but_advance_independently() { // Три "виртуальных" трека внутри одного физического файла (как cue-лист). let mut q = Queue::default(); let file = PathBuf::from("album.flac"); let items = vec![ QueueItem { path: file.clone(), start: std::time::Duration::ZERO, end: Some(std::time::Duration::from_secs(180)), title_override: Some("Track One".into()), artist_override: None, replay_gain_db: None, }, QueueItem { path: file.clone(), start: std::time::Duration::from_secs(180), end: Some(std::time::Duration::from_secs(360)), title_override: Some("Track Two".into()), artist_override: None, replay_gain_db: None, }, QueueItem { path: file.clone(), start: std::time::Duration::from_secs(360), end: None, title_override: Some("Track Three".into()), artist_override: None, replay_gain_db: None, }, ]; q.set_tracks(items, 0); assert_eq!( q.current_item().and_then(|i| i.title_override.clone()), Some("Track One".to_string()) ); assert!(q.advance()); assert_eq!( q.current_item().and_then(|i| i.title_override.clone()), Some("Track Two".to_string()) ); assert!(q.advance()); assert_eq!( q.current_item().and_then(|i| i.title_override.clone()), Some("Track Three".to_string()) ); assert!(!q.advance()); } }