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