Skip to main content

kithara_queue/queue/
access.rs

1use kithara_audio::AudioObserver;
2use kithara_bufpool::HasPool;
3use kithara_events::{EventReceiver, EventSet, TrackId};
4use smallvec::SmallVec;
5
6use super::QueueControl;
7use crate::{
8    event::{QueueEvent, QueueRepeatMode},
9    navigation::{ActionAtItemEnd, PlaybackOrder, RepeatMode},
10    track::{TrackEntry, TrackRecord, TrackSource},
11};
12
13impl<S> QueueControl<S>
14where
15    S: HasPool<u8> + HasPool<f32> + Send + Sync + 'static,
16{
17    #[must_use]
18    pub fn action_at_item_end(&self) -> ActionAtItemEnd {
19        *self
20            .action_at_item_end
21            .lock()
22            .unwrap_or_else(std::sync::PoisonError::into_inner)
23    }
24
25    /// The currently playing track entry, if any.
26    ///
27    /// Sourced from the navigation cursor (not the player) so the queue
28    /// reports `None` after `advance_to_next` runs off the end of the
29    /// queue (`RepeatMode::Off` exhaustion). The player's own
30    /// `current_index` stays parked at the last-played slot — read it
31    /// via [`Self::current_index`] when the call site needs the
32    /// last-played index even after queue-end.
33    #[must_use]
34    pub fn current(&self) -> Option<TrackEntry> {
35        let id = self.lock_navigation().current()?;
36        self.track(id)
37    }
38
39    /// The currently playing track's queue index (player-reported).
40    #[must_use]
41    pub fn current_index(&self) -> Option<usize> {
42        let idx = self.player.current_index();
43        if idx < self.len() { Some(idx) } else { None }
44    }
45
46    pub fn set_action_at_item_end(&self, action: ActionAtItemEnd) {
47        self.command(|queue| {
48            *queue
49                .action_at_item_end
50                .lock()
51                .unwrap_or_else(std::sync::PoisonError::into_inner) = action;
52            queue
53                .bus
54                .publish(QueueEvent::ActionAtItemEndChanged { action });
55        });
56    }
57
58    pub fn set_playback_order(&self, order: PlaybackOrder) {
59        self.command(|queue| {
60            let ids = queue
61                .tracks()
62                .into_iter()
63                .map(|track| track.id)
64                .collect::<SmallVec<[_; 16]>>();
65            queue.lock_navigation_mut().set_playback_order(order, &ids);
66            queue
67                .bus
68                .publish(QueueEvent::PlaybackOrderChanged { order });
69        });
70    }
71
72    /// Set repeat mode.
73    pub fn set_repeat(&self, mode: RepeatMode) {
74        self.command(|queue| {
75            queue.lock_navigation_mut().set_repeat(mode);
76            queue.bus.publish(QueueEvent::RepeatModeChanged {
77                mode: map_repeat_mode(mode),
78            });
79        });
80    }
81
82    /// Subscribe to the unified event stream:
83    /// [`QueueEvent`](crate::event::QueueEvent) + underlying player /
84    /// audio / hls / file events.
85    #[must_use]
86    pub fn subscribe<E: EventSet>(&self) -> EventReceiver<E> {
87        self.bus.subscribe()
88    }
89
90    /// Lookup a track entry by id.
91    #[must_use]
92    pub fn track(&self, id: TrackId) -> Option<TrackEntry> {
93        self.lock_tracks()
94            .iter()
95            .find(|r| r.id == id)
96            .map(TrackRecord::entry)
97    }
98
99    /// The original [`TrackSource`] for `id`, if still queued. Lets callers
100    /// rebuild a resource by track identity rather than by queue position.
101    #[must_use]
102    pub fn track_source(&self, id: TrackId) -> Option<TrackSource<S>> {
103        self.tracks.source(id)
104    }
105
106    delegate::delegate! {
107        to self.loader {
108            /// Attach a bounded decoded-audio observer to `id`'s decoder.
109            ///
110            /// Attachment is nonblocking and works before, during, or after resource
111            /// loading. Only one observer is active for a track at a time.
112            pub fn attach_observer<O: AudioObserver>(&self, id: TrackId, observer: O);
113        }
114        to self.player {
115            /// ABR handle of the currently playing adaptive item, if any.
116            ///
117            /// Returned handle drives runtime variant/bandwidth control — FFI and
118            /// GUI use it for `set_abr_mode` / `set_preferred_peak_bitrate`.
119            #[must_use]
120            pub fn current_abr_handle(&self) -> Option<kithara_abr::AbrHandle>;
121            /// Rate the player's master bus runs at, and therefore the frame axis used
122            /// by decoded-audio observers attached to this queue.
123            #[must_use]
124            pub fn sample_rate(&self) -> u32;
125        }
126        to self {
127            /// Live variant metadata of the currently playing adaptive item.
128            /// Pulled from the player's stashed ABR handle on every call so a
129            /// renderer can poll for the up-to-date label after every frame
130            /// without depending on event delivery.
131            #[must_use]
132            #[expr($?.current_variant())]
133            #[call(current_abr_handle)]
134            pub fn current_variant(&self) -> Option<kithara_abr::VariantInfo>;
135            /// Whether the queue is empty.
136            #[must_use]
137            #[expr($.is_empty())]
138            #[call(lock_tracks)]
139            pub fn is_empty(&self) -> bool;
140            /// Current traversal order.
141            #[must_use]
142            #[expr($.playback_order())]
143            #[call(lock_navigation)]
144            pub fn playback_order(&self) -> PlaybackOrder;
145            /// Number of tracks currently in the queue.
146            #[must_use]
147            #[expr($.len())]
148            #[call(lock_tracks)]
149            pub fn len(&self) -> usize;
150            /// Current repeat mode.
151            #[must_use]
152            #[expr($.repeat_mode())]
153            #[call(lock_navigation)]
154            pub fn repeat_mode(&self) -> RepeatMode;
155            /// Snapshot of all track entries, in queue order.
156            #[must_use]
157            #[expr($.iter().map(TrackRecord::entry).collect())]
158            #[call(lock_tracks)]
159            pub fn tracks(&self) -> Vec<TrackEntry>;
160        }
161    }
162}
163
164const fn map_repeat_mode(mode: RepeatMode) -> QueueRepeatMode {
165    match mode {
166        RepeatMode::Off => QueueRepeatMode::Off,
167        RepeatMode::One => QueueRepeatMode::One,
168        RepeatMode::All => QueueRepeatMode::All,
169    }
170}