Skip to content

Playback Engine

Joseph Chacko edited this page Jul 22, 2026 · 2 revisions

Playback Engine & Audio Pipeline

Relevant source files

The following files were used as context for generating this wiki page:

The Aurora playback engine is a custom streaming architecture built on top of rodio for audio output and symphonia for decoding. It utilizes a buffered, seekable source implementation that allows for seamless track transitions, high-performance seeking, and background decoding.

StateStruct Audio Management

The StateStruct serves as the primary controller for the audio pipeline. It manages the rodio::Sink, the current track metadata, and the active SeekableAudio instance.

Core Methods

Method Description Implementation Details
add() Loads and plays the current song. Clears the sink, initializes SeekableAudio, and spawns a background task to save history. daemon/src/types/state_impl/mod.rs:55-79
pause() Toggles the playback state. Calls sink.play() or sink.pause() based on current state. daemon/src/types/state_impl/mod.rs:81-87
clear() Stops playback and resets state. Clears the sink, empties the queue, and sets current_song to None. daemon/src/types/state_impl/mod.rs:89-95
is_paused() Returns current sink state. Direct proxy to self.sink.is_paused(). daemon/src/types/state_impl/mod.rs:97-99

Playback Control Logic

The playback.rs module extends StateStruct to handle queue navigation. The next() function supports standard linear progression, "Repeat One" logic, and a shuffle mechanism that swaps the next index with a random queue position. daemon/src/types/state_impl/playback.rs:31-61

Sources: daemon/src/types/state_impl/mod.rs:16-30, daemon/src/types/state_impl/mod.rs:55-99, daemon/src/types/state_impl/playback.rs:7-62

Streaming Decoder & Buffer Pipeline

Aurora does not load entire files into memory before playback. Instead, it uses a streaming architecture where a background thread decodes samples into an AudioBuffer while the rodio::Sink consumes them.

SeekableAudio & BufferSource

SeekableAudio manages the lifecycle of a single track's audio data. Upon initialization via SeekableAudio::new, it:

  1. Reads metadata (sample rate, channels, duration) using symphonia. daemon/src/types/state_impl/source.rs:53-58
  2. Spawns a background thread to run decode_streaming. daemon/src/types/state_impl/source.rs:63-66
  3. Wraps the shared AudioBuffer in a BufferSource and appends it to the rodio::Sink. daemon/src/types/state_impl/source.rs:67-69

Seek Implementation

Seeking is performed by stopping the current sink, calculating the new sample index based on the target Duration, and creating a new BufferSource starting at that index. daemon/src/types/state_impl/source.rs:192-208

Audio Pipeline Entity Map

This diagram maps the high-level playback concepts to the specific structs and threads in the aurora-daemon.

graph TD
    subgraph "Natural Language Space"
        A["Playback Request"]
        B["Audio Stream"]
        C["Track Buffer"]
    end

    subgraph "Code Entity Space (aurora-daemon)"
        direction LR
        A1["StateStruct::add()"]
        A2["SeekableAudio::new()"]
        A3["decode_streaming() thread"]
        A4["AudioBuffer"]
        A5["BufferSource (rodio::Source)"]
        A6["rodio::Sink"]

        A1 --> A2
        A2 -.-> A3
        A3 -- "pushes samples" --> A4
        A4 -- "shared Arc/Mutex" --> A5
        A5 -- "consumed by" --> A6
    end

    A --- A1
    B --- A3
    C --- A4
Loading

Sources: daemon/src/types/state_impl/source.rs:45-76, daemon/src/types/state_impl/source.rs:121-182, daemon/src/types/state_impl/source.rs:192-208, daemon/src/types/state_impl/source.rs:212-234

Watcher Thread & Auto-Advance

The watcher_thread is a background loop responsible for track progression. It monitors the rodio::Sink to detect when the current audio source has been exhausted.

Auto-Advance Loop

The thread polls every 100ms. If the sink is empty and the queue is not, it triggers the transition to the next song.

  1. Detection: Checks state_locked.sink.empty(). daemon/src/watcher_thread.rs:11
  2. Transition: Calls state_locked.next(1) followed by state_locked.add(). daemon/src/watcher_thread.rs:12-13
  3. Synchronization: Broadcasts the updated Response::Queue to all connected clients via send_to_all. daemon/src/watcher_thread.rs:15-17

Watcher Thread Flow

sequenceDiagram
    participant W as watcher_thread.rs
    participant S as StateStruct (Locked)
    participant SK as rodio::Sink
    participant C as Connected Clients

    loop Every 100ms
        W->>S: lock()
        S->>SK: empty()?
        alt Sink is Empty AND Queue not Empty
            SK-->>S: true
            S->>S: next(1)
            S->>S: add() (Load next track)
            S->>W: Return updated Queue
            W->>C: send_to_all(Response::Queue)
        else Sink not Empty
            SK-->>S: false
        end
        W->>S: drop(lock)
    end
Loading

Sources: daemon/src/watcher_thread.rs:6-21, daemon/src/types/state_impl/playback.rs:31-61

Album Art Pipeline

The daemon handles album art extraction and caching to ensure the GUI can display high-resolution covers without re-parsing audio files.

  1. Extraction: Uses lofty to read primary tags and extract the Picture data. daemon/src/types/state_impl/mod.rs:121-129
  2. Processing: Decodes the image using the image crate and resizes it to 100x100 pixels using a Nearest filter. daemon/src/types/state_impl/mod.rs:132-141
  3. Caching: Saves the resulting JPEG to the system cache directory (e.g., ~/.cache/aurora-player/) named by the song's UUID. daemon/src/types/state_impl/mod.rs:147-163
  4. Persistence: Spawns a background tokio::task::spawn_blocking to update the art_path in the SQLite database. daemon/src/types/state_impl/mod.rs:171-183

Sources: daemon/src/types/state_impl/mod.rs:101-184

Clone this wiki locally