From 0ae9d89f18bf36407229798a3b4943604fb708e4 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 27 May 2026 20:46:33 +0200 Subject: Add internet streams support --- src/audio.rs | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 4 deletions(-) (limited to 'src/audio.rs') diff --git a/src/audio.rs b/src/audio.rs index e3d88bc..6da47c3 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -6,12 +6,107 @@ */ use crate::notification; -use std::path::PathBuf; +use std::io::{Read, Seek, SeekFrom}; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; +struct HttpMediaSource { + reader: Mutex>, + buffer: Vec, + position: usize, + eof: bool, +} + +impl HttpMediaSource { + fn new(reader: Box) -> Self { + HttpMediaSource { + reader: Mutex::new(reader), + buffer: Vec::new(), + position: 0, + eof: false, + } + } +} + +impl Read for HttpMediaSource { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if self.position < self.buffer.len() { + let available = self.buffer.len() - self.position; + let to_copy = buf.len().min(available); + buf[..to_copy].copy_from_slice(&self.buffer[self.position..self.position + to_copy]); + self.position += to_copy; + return Ok(to_copy); + } + if self.eof { + return Ok(0); + } + let n = self.reader.lock().unwrap().read(buf)?; + if n > 0 { + self.buffer.extend_from_slice(&buf[..n]); + self.position += n; + } else { + self.eof = true; + } + Ok(n) + } +} + +impl Seek for HttpMediaSource { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + match pos { + SeekFrom::Start(offset) => { + let offset = offset as usize; + while offset > self.buffer.len() && !self.eof { + let mut temp = [0u8; 8192]; + let n = self.reader.lock().unwrap().read(&mut temp)?; + if n == 0 { + self.eof = true; + } else { + self.buffer.extend_from_slice(&temp[..n]); + } + } + self.position = offset.min(self.buffer.len()); + Ok(self.position as u64) + } + SeekFrom::End(_) => Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "cannot seek from end on a stream", + )), + SeekFrom::Current(offset) => { + let new_pos = if offset >= 0 { + self.position.saturating_add(offset as usize) + } else { + self.position.saturating_sub((-offset) as usize) + }; + while new_pos > self.buffer.len() && !self.eof { + let mut temp = [0u8; 8192]; + let n = self.reader.lock().unwrap().read(&mut temp)?; + if n == 0 { + self.eof = true; + } else { + self.buffer.extend_from_slice(&temp[..n]); + } + } + self.position = new_pos.min(self.buffer.len()); + Ok(self.position as u64) + } + } + } +} + +impl symphonia::core::io::MediaSource for HttpMediaSource { + fn is_seekable(&self) -> bool { + true + } + + fn byte_len(&self) -> Option { + None + } +} + #[allow(dead_code)] pub enum AudioCommand { Play(PathBuf), @@ -426,10 +521,38 @@ fn open_audio_file(path: &PathBuf) -> Result