Skip to main content

koan_core/player/
commands.rs

1use std::path::PathBuf;
2
3use crossbeam_channel::{Receiver, Sender, bounded};
4
5use super::state::{PlaylistItem, QueueItemId};
6
7/// Commands from the UI layer to the audio engine.
8#[derive(Debug)]
9pub enum PlayerCommand {
10    /// Set cursor + start playback. Replaces Play/SkipTo/SkipBack/PlayInterrupt.
11    Play(QueueItemId),
12    Pause,
13    Resume,
14    Stop,
15    Seek(u64), // position in ms
16    NextTrack,
17    PrevTrack,
18    AddToPlaylist(Vec<PlaylistItem>),
19    RemoveFromPlaylist(QueueItemId),
20    /// Batch remove: delete multiple items as a single undoable operation.
21    RemoveFromPlaylistBatch(Vec<QueueItemId>),
22    MoveInPlaylist {
23        id: QueueItemId,
24        target: QueueItemId,
25        after: bool,
26    },
27    /// Batch move: extract `ids` and reinsert them at `target` position.
28    MoveItemsInPlaylist {
29        ids: Vec<QueueItemId>,
30        target: QueueItemId,
31        after: bool,
32    },
33    /// Put the queue in exactly this order, keeping every item.
34    ///
35    /// A queue locked to a playlist follows it, and following a reorder means
36    /// moving the items that are already there — rebuilding them would issue
37    /// new ids and throw away what has played, what is mid-download and where
38    /// the cursor is. Ids not named keep their relative order at the end.
39    ReorderPlaylist(Vec<QueueItemId>),
40    /// Update file paths for playlist items after an organize operation.
41    /// On Unix, rename() doesn't invalidate open FDs so playback continues.
42    UpdatePaths(Vec<(QueueItemId, PathBuf)>),
43    /// Insert items after a specific queue item (for drag/drop at cursor position).
44    InsertInPlaylist {
45        items: Vec<PlaylistItem>,
46        after: QueueItemId,
47    },
48    /// Clear the entire playlist (stop + remove all items).
49    ClearPlaylist,
50    /// Replace the playlist and start playing at `start`, as one operation.
51    ///
52    /// Doing this as ClearPlaylist + AddToPlaylist + Play sends three commands
53    /// down a bounded channel, and the player acts on each as it arrives: the
54    /// first track starts, then the cursor jumps. Clicking track nine of an
55    /// album visibly flashed track one as playing first. It is also three undo
56    /// entries for one user action.
57    ///
58    /// `start` past the end starts at the beginning.
59    ReplacePlaylist {
60        items: Vec<PlaylistItem>,
61        start: usize,
62    },
63    /// Download complete — check if cursor is waiting on this item.
64    TrackReady(QueueItemId),
65    /// Enough data buffered for streaming playback — check if cursor is waiting.
66    TrackStreamReady(QueueItemId),
67    /// A partial file has been probed off-thread and can be started.
68    ///
69    /// Probing reads as much of the container as it takes to describe itself,
70    /// which for Ogg means its last page — the whole remaining download. That
71    /// cannot happen on this loop, so it happens on its own thread and arrives
72    /// here as a command like anything else. Stale by the time it lands is the
73    /// normal case, and simply ignored.
74    StreamProbed {
75        id: QueueItemId,
76        info: Box<crate::audio::buffer::StreamInfo>,
77        /// What the probe had to settle for. Decoding has to be opened the same
78        /// way — given a length, a container that went looking for its tail
79        /// once will do it again, on the decode thread, where the cost is
80        /// silence instead of a busy player.
81        mode: crate::audio::streaming::ProbeMode,
82    },
83    /// Download failed — a cursor parked on this item must stop waiting.
84    ///
85    /// Without it the player sits on a `Pending` item forever: `Ready` is the
86    /// only thing it listens for, and a track that cannot be fetched never
87    /// becomes Ready. That is the offline-library stall.
88    TrackFailed(QueueItemId),
89    /// Decode thread exhausted the playlist — auto-advance or stop.
90    DecodeFinished,
91    /// Undo the last reversible playlist operation.
92    Undo,
93    /// Redo the last undone operation.
94    Redo,
95    /// Begin collecting undo entries into a single batch (e.g. drag operations).
96    BeginUndoBatch,
97    /// End the batch — collapse collected entries into one undo step.
98    EndUndoBatch,
99    /// Switch output audio device by name. Restarts engine on current track.
100    SetOutputDevice(String),
101    /// Clear the configured output device, reverting to system default.
102    ClearOutputDevice,
103}
104
105/// Bounded SPSC command channel.
106///
107/// Small capacity — we don't want commands queuing up. If the engine is busy,
108/// the UI should know about it, not silently buffer 50 seeks.
109pub struct CommandChannel {
110    pub tx: Sender<PlayerCommand>,
111    pub rx: Receiver<PlayerCommand>,
112}
113
114impl Default for CommandChannel {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120impl CommandChannel {
121    pub fn new() -> Self {
122        let (tx, rx) = bounded(16);
123        Self { tx, rx }
124    }
125}