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    /// Update file paths for playlist items after an organize operation.
34    /// On Unix, rename() doesn't invalidate open FDs so playback continues.
35    UpdatePaths(Vec<(QueueItemId, PathBuf)>),
36    /// Insert items after a specific queue item (for drag/drop at cursor position).
37    InsertInPlaylist {
38        items: Vec<PlaylistItem>,
39        after: QueueItemId,
40    },
41    /// Clear the entire playlist (stop + remove all items).
42    ClearPlaylist,
43    /// Replace the playlist and start playing at `start`, as one operation.
44    ///
45    /// Doing this as ClearPlaylist + AddToPlaylist + Play sends three commands
46    /// down a bounded channel, and the player acts on each as it arrives: the
47    /// first track starts, then the cursor jumps. Clicking track nine of an
48    /// album visibly flashed track one as playing first. It is also three undo
49    /// entries for one user action.
50    ///
51    /// `start` past the end starts at the beginning.
52    ReplacePlaylist {
53        items: Vec<PlaylistItem>,
54        start: usize,
55    },
56    /// Download complete — check if cursor is waiting on this item.
57    TrackReady(QueueItemId),
58    /// Enough data buffered for streaming playback — check if cursor is waiting.
59    TrackStreamReady(QueueItemId),
60    /// Download failed — a cursor parked on this item must stop waiting.
61    ///
62    /// Without it the player sits on a `Pending` item forever: `Ready` is the
63    /// only thing it listens for, and a track that cannot be fetched never
64    /// becomes Ready. That is the offline-library stall.
65    TrackFailed(QueueItemId),
66    /// Decode thread exhausted the playlist — auto-advance or stop.
67    DecodeFinished,
68    /// Undo the last reversible playlist operation.
69    Undo,
70    /// Redo the last undone operation.
71    Redo,
72    /// Begin collecting undo entries into a single batch (e.g. drag operations).
73    BeginUndoBatch,
74    /// End the batch — collapse collected entries into one undo step.
75    EndUndoBatch,
76    /// Switch output audio device by name. Restarts engine on current track.
77    SetOutputDevice(String),
78    /// Clear the configured output device, reverting to system default.
79    ClearOutputDevice,
80}
81
82/// Bounded SPSC command channel.
83///
84/// Small capacity — we don't want commands queuing up. If the engine is busy,
85/// the UI should know about it, not silently buffer 50 seeks.
86pub struct CommandChannel {
87    pub tx: Sender<PlayerCommand>,
88    pub rx: Receiver<PlayerCommand>,
89}
90
91impl Default for CommandChannel {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl CommandChannel {
98    pub fn new() -> Self {
99        let (tx, rx) = bounded(16);
100        Self { tx, rx }
101    }
102}