Skip to main content

kithara_queue/queue/selection/
navigation.rs

1use kithara_bufpool::HasPool;
2use kithara_events::TrackId;
3use smallvec::SmallVec;
4use tracing::debug;
5
6use crate::{
7    error::QueueError,
8    event::{AdvanceReason, QueueEvent, TrackStatus},
9    navigation::RepeatMode,
10    queue::{QueueControl, types::Transition},
11    track::TrackEntry,
12};
13
14impl<S> QueueControl<S>
15where
16    S: HasPool<u8> + HasPool<f32> + Send + Sync + 'static,
17{
18    pub(in crate::queue) fn advance_to_next_inner(
19        &self,
20        transition: Transition,
21        reason: AdvanceReason,
22    ) -> Result<Option<TrackId>, QueueError> {
23        let Some(next) = self.next_selectable_entry(reason) else {
24            if matches!(
25                reason,
26                AdvanceReason::NaturalEof
27                    | AdvanceReason::TrackFailed
28                    | AdvanceReason::CrossfadePreArm
29            ) {
30                self.lock_navigation_mut().finish();
31                self.bus.publish(QueueEvent::QueueEnded);
32            }
33            return Ok(None);
34        };
35        let id = next.id;
36        self.select_with_reason(id, transition, reason)?;
37        Ok(Some(id))
38    }
39
40    /// Advance to the next track per navigation rules. Returns the newly
41    /// selected id, or `None` when the queue has ended (and
42    /// [`RepeatMode::Off`](crate::navigation::RepeatMode::Off) is active).
43    ///
44    /// # Errors
45    ///
46    /// Returns a queue or player error when the successor cannot be selected.
47    pub fn next(&self, transition: Transition) -> Result<Option<TrackId>, QueueError> {
48        self.with_open_result(|queue| {
49            queue.advance_to_next_inner(transition, AdvanceReason::UserNext)
50        })
51    }
52
53    /// Read the next selectable entry without mutating navigation. Selection
54    /// commits navigation only when the player selection actually commits.
55    pub(in crate::queue) fn next_selectable_entry(
56        &self,
57        reason: AdvanceReason,
58    ) -> Option<TrackEntry> {
59        let tracks = self.lock_tracks();
60        let selectable = tracks
61            .iter()
62            .filter(|record| {
63                let available = !matches!(
64                    record.status,
65                    TrackStatus::Cancelled | TrackStatus::Failed(_)
66                );
67                if !available {
68                    debug!(
69                        id = record.id.as_u64(),
70                        "navigation skipped unavailable track"
71                    );
72                }
73                available
74            })
75            .map(crate::track::TrackRecord::entry)
76            .collect::<Vec<TrackEntry>>();
77        drop(tracks);
78        let ids = selectable
79            .iter()
80            .map(|entry| entry.id)
81            .collect::<SmallVec<[_; 16]>>();
82        let mut navigation = self.lock_navigation_mut();
83        let automatic = matches!(
84            reason,
85            AdvanceReason::NaturalEof | AdvanceReason::TrackFailed | AdvanceReason::CrossfadePreArm
86        );
87        let allow_repeat_one = matches!(
88            reason,
89            AdvanceReason::NaturalEof | AdvanceReason::CrossfadePreArm
90        );
91        let allow_wrap = automatic && navigation.repeat_mode() == RepeatMode::All;
92        let id = navigation.next(&ids, allow_repeat_one, allow_wrap)?;
93        drop(navigation);
94        selectable.into_iter().find(|entry| entry.id == id)
95    }
96
97    pub(in crate::queue) fn peek_selectable_entry(&self) -> Option<TrackEntry> {
98        let selectable = self
99            .lock_tracks()
100            .iter()
101            .filter(|record| {
102                !matches!(
103                    record.status,
104                    TrackStatus::Cancelled | TrackStatus::Failed(_)
105                )
106            })
107            .map(crate::track::TrackRecord::entry)
108            .collect::<Vec<TrackEntry>>();
109        let ids = selectable
110            .iter()
111            .map(|entry| entry.id)
112            .collect::<SmallVec<[_; 16]>>();
113        let id = self.lock_navigation().peek_next(&ids)?;
114        selectable.into_iter().find(|entry| entry.id == id)
115    }
116
117    /// Go back to the previous track. Returns the newly selected id, or
118    /// `None` at index 0.
119    ///
120    /// # Errors
121    ///
122    /// Returns a queue or player error when the predecessor cannot be selected.
123    pub fn previous(&self, transition: Transition) -> Result<Option<TrackId>, QueueError> {
124        self.with_open_result(|queue| queue.return_to_previous_inner(transition))
125    }
126
127    fn return_to_previous_inner(
128        &self,
129        transition: Transition,
130    ) -> Result<Option<TrackId>, QueueError> {
131        let tracks = self.tracks();
132        let ids = tracks
133            .iter()
134            .map(|entry| entry.id)
135            .collect::<SmallVec<[_; 16]>>();
136        let Some(id) = self.lock_navigation_mut().prev(&ids) else {
137            return Ok(None);
138        };
139        self.select_with_reason(id, transition, AdvanceReason::UserPrev)?;
140        Ok(Some(id))
141    }
142}