using DeepDrftModels.DTOs; namespace DeepDrftPublic.Client.Services; /// /// Orchestrates ordered playback ("what plays next") above the single-slot /// . The player stays a single-track device; the queue owns the /// track list, the current position, skip-forward/back, and auto-advance on natural track end. It /// drives playback solely through the player's existing /// — it adds no new playback semantics. /// /// /// Extension posture (open/closed): future shuffle, repeat modes, reordering, and persistence are /// expected. They are additive — a shuffle/repeat strategy slots in behind / /// as the "which index is next" decision; reordering mutates /// and re-emits ; persistence snapshots/restores + /// . None of those require changing this interface's existing members, only /// adding new ones — so consumers written against today's surface keep working. /// /// /// /// With an empty queue ( == -1) the queue is dormant: it drives nothing and /// auto-advances nothing, so direct single-track play through the player behaves exactly as it did /// before the queue existed. /// /// public interface IQueueService { /// The ordered tracks currently queued. Empty when nothing is enqueued. IReadOnlyList Items { get; } /// /// Index into of the track the queue considers current, or -1 when the /// queue is empty. Always a valid index into when non-negative. /// int CurrentIndex { get; } /// The current track, or null when the queue is empty. TrackDto? Current { get; } /// /// True when the queue has been loaded via but no track has streamed yet — /// the embed's pre-gesture state. Set by ; cleared the moment playback actually /// starts (///) /// or on . The player bar reads this to route the first play gesture through /// (which begins the armed release) rather than streaming the staged track alone. /// bool IsArmed { get; } /// True when there is a track after to advance to. bool HasNext { get; } /// True when there is a track before to step back to. bool HasPrevious { get; } /// /// Raised whenever the queue's contents or current position change. The player bar subscribes /// to re-render its skip-forward/back affordances. Fires on enqueue, advance, step-back, and clear. /// event Action? QueueChanged; /// /// Replaces the queue with (in the order given) and begins streaming /// the track at . This is the "play album" entry point the Cuts /// detail page consumes: pass the release's tracks in ordinal order. A header Play uses /// startIndex: 0; a mid-album row play passes that row's index so the queue continues to /// the end from there. No-op when is empty. /// Task PlayRelease(IEnumerable tracks, int startIndex = 0); /// /// Loads as the queue and sets the current position to index 0 WITHOUT /// streaming anything — the queue is "armed". This is the embed's prerender-safe entry point: it /// performs no JS interop, so it runs identically during prerender and after WASM boot. The first /// play gesture (see ) then starts playback via , which /// keeps the loaded release queued so it advances through its tracks. No-op when /// is empty (the queue stays empty and disarmed). /// void Arm(IEnumerable tracks); /// /// Begins playback of an armed queue (see ): streams the current track and clears /// , leaving the loaded list and position intact so auto-advance carries on /// through the release. This is the first-gesture entry point the embed bar calls. No-op (and stays /// disarmed) when the queue is not armed or is empty — so it never double-streams or disturbs a /// queue already playing. /// Task Start(); /// /// Appends a track to the end of the queue without changing what is currently playing. /// Into a dormant queue ( == -1) the append leaves a coherent /// (the first appended track) so a subsequent play/skip is correct — /// but it does NOT begin playback (add is not play). Interop-free; safe during prerender. /// void Enqueue(TrackDto track); /// /// Appends tracks to the end of the queue without changing what is currently playing. /// Into a dormant queue ( == -1) the append leaves a coherent /// (the first appended track) so a subsequent play/skip is correct — /// but it does NOT begin playback (add is not play). Interop-free; safe during prerender. /// void EnqueueRange(IEnumerable tracks); /// /// Reorders the queue, moving the track at to /// , and re-emits . Adjusts /// so the same track stays current across the move — it does /// not restart, re-stream, or interrupt the currently-playing track. Interop-free; safe during /// prerender. No-op (no throw, no ) when either index is out of range /// or the indices are equal. /// void Move(int fromIndex, int toIndex); /// /// Removes the track at and re-emits . Does /// not touch playback (the player stays a single-track device): removing the current track does /// not stop it — the playing track runs to its natural end while /// resolves to the new occupant of that slot (the next track) so the next auto-advance/skip is /// coherent. Removing a track before the current decrements (the same /// track stays current); removing after the current leaves it unchanged. Removing the last /// remaining track empties the queue ( == -1, dormant). Interop-free; /// safe during prerender. No-op (no throw, no ) when /// is out of range. /// void RemoveAt(int index); /// /// Advances to the next track and streams it. No-op when is false. /// Task Next(); /// /// Steps back to the previous track and streams it. No-op when is false. /// Task Previous(); /// Empties the queue and resets the position. Does not stop the player. void Clear(); /// /// Empties the up-next while keeping the currently-playing track: removes every item except /// , leaving it as the sole remaining item at == 0, /// and re-emits . Unlike (which empties everything and /// goes dormant), this preserves what is playing — the player is never stopped and the current track /// stays queued, so playback continues uninterrupted while the rest of the queue is discarded. /// Interop-free; safe during prerender. No-op (no throw, no ) when the queue /// is empty/dormant or already holds only the current track. /// void ClearUpcoming(); }