melors 0.2.2

Keyboard-first terminal MP3 player with queue, search, and tag editing
use super::*;

impl Player {
    pub(super) fn build_sink(
        mixer: &rodio::mixer::Mixer,
        path: &Path,
        start_secs: u64,
    ) -> Result<RodioPlayer> {
        let file =
            File::open(path).with_context(|| format!("failed to open {}", path.display()))?;
        let reader = BufReader::new(file);
        let decoder = Decoder::new(reader).context("failed to decode audio file")?;
        let source = decoder.skip_duration(Duration::from_secs(start_secs));
        let player = RodioPlayer::connect_new(mixer);
        player.append(source);
        Ok(player)
    }

    fn analyze_file(_path: &Path) -> Result<VisualizerAnalysis> {
        Ok(VisualizerAnalysis {
            frames: vec![vec![0.0; ANALYSIS_BANDS]],
            frame_duration_secs: 0.2,
        })
    }

    pub(super) fn insert_analysis_cache(
        &mut self,
        key: AnalysisCacheKey,
        analysis: VisualizerAnalysis,
    ) {
        if let Some(cached) = self.analysis_cache.get_mut(&key) {
            *cached = analysis;
            return;
        }

        if self.analysis_cache_order.len() >= MAX_ANALYSIS_CACHE_ITEMS
            && let Some(oldest) = self.analysis_cache_order.pop_front()
        {
            self.analysis_cache.remove(&oldest);
        }

        self.analysis_cache_order.push_back(key.clone());
        self.analysis_cache.insert(key, analysis);
    }

    pub(super) fn spawn_analysis(&mut self, path: PathBuf, key: AnalysisCacheKey) {
        if self.analysis_pending.contains(&key) {
            return;
        }
        self.analysis_pending.insert(key.clone());
        self.analysis_queue.push_back((path, key));

        while self.analysis_queue.len() > MAX_QUEUED_ANALYSIS_JOBS {
            if let Some((_, dropped_key)) = self.analysis_queue.pop_front() {
                self.analysis_pending.remove(&dropped_key);
            }
        }

        self.try_start_analysis_jobs();
    }

    pub(super) fn try_start_analysis_jobs(&mut self) {
        while self.analysis_active_jobs < MAX_CONCURRENT_ANALYSIS_JOBS {
            let Some((path, key)) = self.analysis_queue.pop_front() else {
                break;
            };

            self.analysis_active_jobs += 1;
            let tx = self.analysis_tx.clone();
            std::thread::spawn(move || {
                let analysis = Self::analyze_file(&path).ok();
                let _ = tx.send((key, analysis));
            });
        }
    }
}