lavende-core 0.1.7

Core in-process Discord voice connection and playback engine
Documentation
use crate::{
    audio::{
        AudioFrame,
        processor::{AudioProcessor, DecoderCommand},
    },
    common::AudioFormat,
    config::player::PlayerConfig,
    sources::SourcePlugin,
};
use async_trait::async_trait;
use flume::{Receiver, Sender};
use std::sync::Arc;
pub type DecoderOutput = (
    Receiver<AudioFrame>,
    Sender<DecoderCommand>,
    Receiver<String>,
);
pub type BoxedTrack = Arc<dyn PlayableTrack>;
pub type BoxedSource = Box<dyn SourcePlugin>;
pub struct ResolvedTrack {
    pub reader: Box<dyn symphonia::core::io::MediaSource>,
    pub hint: Option<AudioFormat>,
}
impl ResolvedTrack {
    pub fn new(
        reader: Box<dyn symphonia::core::io::MediaSource>,
        hint: Option<AudioFormat>,
    ) -> Self {
        Self { reader, hint }
    }
    pub fn without_hint(reader: Box<dyn symphonia::core::io::MediaSource>) -> Self {
        Self { reader, hint: None }
    }
}
#[async_trait]
pub trait PlayableTrack: Send + Sync + 'static {
    async fn resolve(&self) -> Result<ResolvedTrack, String>;
    fn supports_seek(&self) -> bool {
        false
    }
    fn start_decoding(self: Arc<Self>, config: PlayerConfig) -> DecoderOutput {
        let (tx, rx) =
            flume::bounded::<AudioFrame>((config.buffer_duration_ms / 20).max(200) as usize);
        let (cmd_tx, cmd_rx) = flume::unbounded::<DecoderCommand>();
        let (err_tx, err_rx) = flume::bounded::<String>(1);
        let supports_seek = self.supports_seek();
        tokio::spawn(async move {
            let ResolvedTrack { reader, hint } = match self.resolve().await {
                Ok(r) => r,
                Err(e) => {
                    let _ = err_tx.send(e);
                    return;
                }
            };
            let err_tx_clone = err_tx.clone();
            tokio::task::spawn_blocking(move || {
                match AudioProcessor::new(
                    reader,
                    hint,
                    tx,
                    cmd_rx,
                    Some(err_tx_clone.clone()),
                    config,
                ) {
                    Ok(mut processor) => {
                        let result = if supports_seek {
                            processor.run_with_seek()
                        } else {
                            processor.run()
                        };
                        if let Err(e) = result {
                            let _ = err_tx_clone.send(format!(
                                "Playback failed: {e} (hint={hint:?}, seek={supports_seek})"
                            ));
                        }
                    }
                    Err(e) => {
                        let _ = err_tx_clone.send(format!(
                            "Failed to initialize audio processor: {e} (hint={hint:?})"
                        ));
                    }
                }
            });
        });
        (rx, cmd_tx, err_rx)
    }
}