kithara-queue 0.0.1-alpha5

Queue/playlist orchestration: gapless, crossfade-aware.
Documentation

crates.io docs.rs License

kithara-queue

AVQueuePlayer-analogue orchestration layer on top of kithara-play. Owns the queue (ordered tracks), an async track loader with a configurable parallelism cap, navigation (shuffle / repeat / history), and crossfade-aware track selection. Replaces the bespoke queue / controller code previously duplicated across kithara-app and future iOS / Android SDK surfaces.

Usage

use std::sync::Arc;

use kithara_bufpool::{OverallBudget, PoolConfig, PoolError, pool_schema};
use kithara_play::{PlayWorker, PlayWorkerConfig, PlayerConfig, PlayerImpl};
use kithara_queue::{Queue, QueueConfig, Transition};

pool_schema! {
    pub AppPools {
        bytes: u8,
        samples: f32,
    }
}

#[tokio::main]
async fn main() -> Result<(), PoolError> {
    let config = || PoolConfig::builder().max_buffers(128).build();
    let pools = AppPools::builder(OverallBudget(64 * 1024 * 1024))
        .bytes(config())
        .samples(config())
        .build()?;
    let worker = PlayWorker::new(PlayWorkerConfig::builder(pools).build());
    let player = PlayerImpl::new(
        PlayerConfig::builder().worker(worker).build(),
    );
    let queue = Arc::new(Queue::new(
        QueueConfig::builder().player(player).build(),
    ));
    queue.set_tracks(["https://example.com/a.mp3", "https://example.com/b.mp3"]);

    // Caller explicitly picks the first track to play. Queue autoplays
    // only when built with `should_autoplay(true)`; otherwise the UI (or
    // any other caller) calls `select` / `play` when the user is ready.
    if let Some(first) = queue.tracks().first() {
        let _ = queue.select(first.id, Transition::None);
    }

    let mut rx = queue.subscribe();
    while let Ok(event) = rx.recv().await {
        println!("{event:?}");
    }
    Ok(())
}

Queue<S>, QueueConfig<S>, and TrackSource<S> require the schema to provide both HasPool<u8> and HasPool<f32>. The queue never constructs a pool region. When no store is supplied, it builds the store from the exact PoolRegion<S> already owned by the player, preserving the shared hard budget.

Queue::set_tracks must run inside an active tokio runtime because the loader uses tokio::spawn.

QueueConfig owns the initial playback order, item-end action, and complete crossfade profile. Their runtime setters update that same queue-owned state; repeat remains a separate automatic-EOF rule, and should_autoplay applies only to the first load.

Key Types

[Queue<S>] owns a PlayerImpl<S> (from kithara-play) and composes it with:

  • an ordered Vec<TrackEntry> indexed by stable [TrackId]s,
  • an async [Loader] (internal) that caps in-flight Resource::new calls via a tokio::sync::Semaphore,
  • [NavigationState] for stable-ID sequential or shuffle traversal, repeat, and actual selection history,
  • a pending_select slot so Queue::select(id) can be called before the track has finished loading.

[Queue] emits [QueueEvent] on the shared EventBus from kithara-events, so subscribers receive queue-level signals and the underlying player / audio / hls / file events through a single stream.

  • [Queue::new(QueueConfig)] - the orchestrator, generic over the player's registered pool schema: CRUD (append, insert, remove, clear, set_tracks), navigation (select, next, previous, typed playback order / repeat / item-end action / seek), playback controls delegated to PlayerImpl, and tick() to drive the player and drain engine events.
  • [CrossfadeSettings] — duration, linear or equal-power curve, blend depth, and temporal crossover pivot. Linear controls amplitude and can dip perceived power for unrelated tracks; equal-power approximately preserves uncorrelated power and can raise correlated material.
  • [TrackSource<S>] - input to append / insert / set_tracks, either a Uri(String) (Queue builds a default ResourceConfig) or a Config(Box<ResourceConfig>) (caller-built, for DRM keys / headers / format hints).
  • [QueueEvent] — queue-level signals delivered via [Queue::subscribe] alongside the underlying player / audio / hls / file events.

See crate contracts for detailed contracts, invariants, and internals.