vpr-audio-player 0.1.0

Audio file player
Documentation
mod error;
mod test;

use std::{
    io::{BufReader, Seek},
    time::Duration,
};

use rodio::{Decoder, OutputStream, Sink};

use self::error::{Error, Result};

const MAX_FILE_SIZE_FOR_SEEK_INDEX: u64 = 1024 * 1024 * 50; // 50 MB

pub struct Player {
    sink: Sink,
    _stream: OutputStream,
    std_file_handle: Option<std::fs::File>,
    duration: Option<Duration>,
    seek_index: Option<Vec<(Duration, u64)>>,
}

impl Player {
    pub fn new() -> Self {
        let (_stream, stream_handle) = OutputStream::try_default().unwrap();
        let sink = Sink::try_new(&stream_handle).unwrap();
        Self {
            sink,
            _stream,
            std_file_handle: None,
            duration: None,
            seek_index: None,
        }
    }

    pub fn is_file_loaded(&self) -> bool {
        self.std_file_handle.is_some()
    }

    pub async fn load_path(&mut self, path: &str) -> Result<()> {
        let file = match tokio::fs::File::open(path).await {
            Ok(file) => file,
            Err(_) => return Err(Error::UnableToOpenFile),
        };

        self.load_file(file).await?;

        Ok(())
    }

    pub async fn load_file(&mut self, file: tokio::fs::File) -> Result<()> {
        self.stop();

        let reader = tokio::io::BufReader::new(file.try_clone().await.unwrap());
        let mut analyzer = vpr_audio_analyzer::Analyzer::new(reader);

        self.duration = match analyzer.get_duration().await {
            Ok(duration) => Some(duration),
            Err(_) => None,
        };

        // If the file is too big, we don't want to create a seek index
        // because it would take too long.
        let file_size = file.metadata().await.unwrap().len();
        if file_size <= MAX_FILE_SIZE_FOR_SEEK_INDEX {
            self.seek_index = match analyzer.get_seek_index().await {
                Ok(seek_index) => Some(seek_index),
                Err(_) => None,
            };
        }

        let mut std_file = file.into_std().await;
        self.std_file_handle = match std_file.try_clone() {
            Ok(std_file_handle) => Some(std_file_handle),
            Err(_) => return Err(Error::UnableToCloneFileHandle),
        };

        std_file.seek(std::io::SeekFrom::Start(0)).unwrap();
        let reader = BufReader::new(std_file);
        let source = Decoder::new(reader).unwrap();

        self.sink.append(source);

        Ok(())
    }

    fn get_std_file_handle(&self) -> Result<&std::fs::File> {
        match self.std_file_handle.as_ref() {
            Some(std_file_handle) => Ok(std_file_handle),
            None => return Err(Error::NoFileHandle),
        }
    }

    fn get_bytes_offset_for_time(&self, time: Duration) -> Result<u64> {
        let seek_index = match self.seek_index.as_ref() {
            Some(seek_index) => seek_index,
            None => return Err(Error::NoSeekIndex),
        };

        let mut offset = 0;
        for (frame_time, frame_offset) in seek_index {
            if frame_time > &time {
                break;
            }
            offset = *frame_offset;
        }

        Ok(offset)
    }

    fn get_time_for_bytes_offset(&self, offset: u64) -> Result<Duration> {
        let seek_index = match self.seek_index.as_ref() {
            Some(seek_index) => seek_index,
            None => return Err(Error::NoSeekIndex),
        };

        let mut time = Duration::from_secs(0);
        for (frame_time, frame_offset) in seek_index {
            if frame_offset > &offset {
                break;
            }
            time = *frame_time;
        }

        Ok(time)
    }

    pub fn is_seekable(&self) -> bool {
        self.is_file_loaded() && self.seek_index.is_some()
    }

    pub fn seek(&mut self, time_offset: Duration) -> Result<()> {
        let bytes_offset = self.get_bytes_offset_for_time(time_offset)?;
        let mut std_file_handle = self.get_std_file_handle()?;

        std_file_handle
            .seek(std::io::SeekFrom::Start(bytes_offset))
            .map_err(|_| Error::NotAbleToSeek)?;

        Ok(())
    }

    pub fn play(&mut self) {
        self.sink.play();
    }

    pub fn pause(&mut self) {
        self.sink.pause();
    }

    pub fn stop(&mut self) {
        self.duration = None;
        self.seek_index = None;
        self.std_file_handle = None;
        self.sink.stop();
    }

    pub fn elapsed(&self) -> Result<Duration> {
        let mut file_handle = self.get_std_file_handle()?;
        let cursor_position = file_handle
            .seek(std::io::SeekFrom::Current(0))
            .map_err(|_| Error::NotAbleToSeek)?;

        let elapsed_time = self.get_time_for_bytes_offset(cursor_position)?;
        Ok(elapsed_time)
    }

    pub fn duration(&self) -> Option<Duration> {
        self.duration
    }

    pub fn volume(&self) -> f32 {
        self.sink.volume()
    }

    pub fn set_volume(&mut self, volume: f32) {
        self.sink.set_volume(volume);
    }
}