Skip to main content

koan_core/audio/
streaming.rs

1//! StreamingSource — a Read+Seek adapter over a shared, incrementally-filled byte buffer.
2//!
3//! The download thread writes chunks into a `StreamBuffer` while the Symphonia decoder
4//! reads from a `StreamingSource` backed by the same buffer. The source blocks briefly
5//! when the read position catches up to the write head, enabling true streaming decode
6//! without waiting for the full download to complete.
7
8use std::io::{self, Read, Seek, SeekFrom};
9use std::sync::{Arc, Condvar, Mutex};
10
11/// State shared between the download writer and the decoder reader.
12struct Inner {
13    data: Vec<u8>,
14    /// Total expected byte length. `None` if not yet known (no Content-Length).
15    total_len: Option<u64>,
16    /// Set to true when the download thread has finished (EOF or error).
17    done: bool,
18}
19
20/// A shared, growable byte buffer that the download thread writes into.
21///
22/// Clone it to get additional handles; all clones share the same underlying data.
23#[derive(Clone)]
24pub struct StreamBuffer {
25    inner: Arc<(Mutex<Inner>, Condvar)>,
26}
27
28impl StreamBuffer {
29    /// Create a new empty buffer. `total_len` may be provided once Content-Length is known.
30    pub fn new(total_len: Option<u64>) -> Self {
31        Self {
32            inner: Arc::new((
33                Mutex::new(Inner {
34                    data: Vec::new(),
35                    total_len,
36                    done: false,
37                }),
38                Condvar::new(),
39            )),
40        }
41    }
42
43    /// Append downloaded bytes. Called by the download thread.
44    pub fn push(&self, chunk: &[u8]) {
45        let (lock, cvar) = &*self.inner;
46        let mut inner = lock.lock().unwrap();
47        inner.data.extend_from_slice(chunk);
48        cvar.notify_all();
49    }
50
51    /// Signal that the download is complete (or failed). No more bytes will arrive.
52    pub fn finish(&self) {
53        let (lock, cvar) = &*self.inner;
54        let mut inner = lock.lock().unwrap();
55        inner.done = true;
56        cvar.notify_all();
57    }
58
59    /// Total bytes received so far.
60    pub fn bytes_downloaded(&self) -> u64 {
61        let (lock, _) = &*self.inner;
62        lock.lock().unwrap().data.len() as u64
63    }
64
65    /// Total expected length (from Content-Length), if known.
66    pub fn total_len(&self) -> Option<u64> {
67        let (lock, _) = &*self.inner;
68        lock.lock().unwrap().total_len
69    }
70
71    /// Create a `StreamingSource` that reads from this buffer starting at offset 0.
72    pub fn reader(&self) -> StreamingSource {
73        StreamingSource {
74            inner: self.inner.clone(),
75            pos: 0,
76        }
77    }
78}
79
80/// A `Read + Seek` view into a `StreamBuffer`.
81///
82/// Blocks on `read` when the read position is at or beyond the write head,
83/// until more bytes arrive or the download finishes.
84pub struct StreamingSource {
85    inner: Arc<(Mutex<Inner>, Condvar)>,
86    pos: u64,
87}
88
89impl Read for StreamingSource {
90    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
91        if buf.is_empty() {
92            return Ok(0);
93        }
94
95        let (lock, cvar) = &*self.inner;
96
97        // Wait until there is data at `pos`, or the download is done.
98        let inner = cvar
99            .wait_while(lock.lock().unwrap(), |s| {
100                s.data.len() as u64 <= self.pos && !s.done
101            })
102            .unwrap();
103
104        let available = inner.data.len() as u64;
105        if available <= self.pos {
106            // Done and no more data — EOF.
107            return Ok(0);
108        }
109
110        let start = self.pos as usize;
111        let end = (start + buf.len()).min(inner.data.len());
112        let n = end - start;
113        buf[..n].copy_from_slice(&inner.data[start..end]);
114        self.pos += n as u64;
115        Ok(n)
116    }
117}
118
119impl Seek for StreamingSource {
120    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
121        let (lock, _) = &*self.inner;
122        let inner = lock.lock().unwrap();
123
124        let new_pos: i64 = match pos {
125            SeekFrom::Start(n) => n as i64,
126            SeekFrom::Current(n) => self.pos as i64 + n,
127            SeekFrom::End(n) => {
128                // For End seeks we need total_len. If not known yet, use current data len.
129                let len = inner.total_len.unwrap_or(inner.data.len() as u64) as i64;
130                len + n
131            }
132        };
133
134        if new_pos < 0 {
135            return Err(io::Error::new(
136                io::ErrorKind::InvalidInput,
137                "seek before beginning of stream",
138            ));
139        }
140
141        self.pos = new_pos as u64;
142        Ok(self.pos)
143    }
144}
145
146// Symphonia requires MediaSource: Read + Seek + Send + Any
147impl symphonia::core::io::MediaSource for StreamingSource {
148    fn is_seekable(&self) -> bool {
149        // Seekable only if the total length is known (needed for seek-to-end math).
150        // Forward seeks always work; backward seeks require buffered data already present.
151        // We advertise seekable=true and handle backward seeks via the buffered Vec.
152        true
153    }
154
155    fn byte_len(&self) -> Option<u64> {
156        let (lock, _) = &*self.inner;
157        lock.lock().unwrap().total_len
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use std::io::{Read, Seek, SeekFrom};
164
165    use super::*;
166
167    fn filled_buffer(data: &[u8]) -> StreamBuffer {
168        let buf = StreamBuffer::new(Some(data.len() as u64));
169        buf.push(data);
170        buf.finish();
171        buf
172    }
173
174    #[test]
175    fn new_buffer_starts_empty() {
176        let buf = StreamBuffer::new(Some(1024));
177        assert_eq!(buf.bytes_downloaded(), 0);
178        assert_eq!(buf.total_len(), Some(1024));
179    }
180
181    #[test]
182    fn new_buffer_unknown_total() {
183        let buf = StreamBuffer::new(None);
184        assert_eq!(buf.total_len(), None);
185    }
186
187    #[test]
188    fn read_all_data_available() {
189        let data = b"hello streaming world";
190        let buf = filled_buffer(data);
191        let mut src = buf.reader();
192
193        let mut out = Vec::new();
194        src.read_to_end(&mut out).unwrap();
195        assert_eq!(out, data);
196    }
197
198    #[test]
199    fn read_partial_then_rest() {
200        let data = b"abcdefghij";
201        let buf = filled_buffer(data);
202        let mut src = buf.reader();
203
204        let mut first = [0u8; 4];
205        let n = src.read(&mut first).unwrap();
206        assert_eq!(n, 4);
207        assert_eq!(&first, b"abcd");
208
209        let mut rest = Vec::new();
210        src.read_to_end(&mut rest).unwrap();
211        assert_eq!(rest, b"efghij");
212    }
213
214    #[test]
215    fn seek_from_start() {
216        let data = b"0123456789";
217        let buf = filled_buffer(data);
218        let mut src = buf.reader();
219
220        let pos = src.seek(SeekFrom::Start(5)).unwrap();
221        assert_eq!(pos, 5);
222
223        let mut out = [0u8; 3];
224        src.read_exact(&mut out).unwrap();
225        assert_eq!(&out, b"567");
226    }
227
228    #[test]
229    fn seek_from_current() {
230        let data = b"0123456789";
231        let buf = filled_buffer(data);
232        let mut src = buf.reader();
233
234        src.seek(SeekFrom::Start(2)).unwrap();
235        let pos = src.seek(SeekFrom::Current(3)).unwrap();
236        assert_eq!(pos, 5);
237
238        let mut out = [0u8; 2];
239        src.read_exact(&mut out).unwrap();
240        assert_eq!(&out, b"56");
241    }
242
243    #[test]
244    fn seek_from_end() {
245        let data = b"0123456789";
246        let buf = filled_buffer(data);
247        let mut src = buf.reader();
248
249        // SeekFrom::End(0) should position at total_len (EOF).
250        let pos = src.seek(SeekFrom::End(0)).unwrap();
251        assert_eq!(pos, 10);
252
253        // SeekFrom::End(-3) should position at offset 7.
254        let pos = src.seek(SeekFrom::End(-3)).unwrap();
255        assert_eq!(pos, 7);
256
257        let mut out = [0u8; 3];
258        src.read_exact(&mut out).unwrap();
259        assert_eq!(&out, b"789");
260    }
261
262    #[test]
263    fn seek_before_start_errors() {
264        let data = b"hello";
265        let buf = filled_buffer(data);
266        let mut src = buf.reader();
267
268        let result = src.seek(SeekFrom::Current(-1));
269        assert!(result.is_err());
270    }
271
272    #[test]
273    fn is_complete_when_done() {
274        let buf = StreamBuffer::new(Some(5));
275        buf.push(b"hello");
276        // Not yet finished.
277        assert!(buf.bytes_downloaded() != 0); // just check bytes_downloaded works
278        buf.finish();
279        // After finish, a reader should see EOF immediately.
280        let mut src = buf.reader();
281        let mut out = Vec::new();
282        src.read_to_end(&mut out).unwrap();
283        assert_eq!(out, b"hello");
284    }
285
286    #[test]
287    fn byte_len_returns_total() {
288        use symphonia::core::io::MediaSource;
289        let buf = StreamBuffer::new(Some(42));
290        let src = buf.reader();
291        assert_eq!(src.byte_len(), Some(42));
292    }
293
294    #[test]
295    fn is_seekable_true() {
296        use symphonia::core::io::MediaSource;
297        let buf = StreamBuffer::new(None);
298        let src = buf.reader();
299        assert!(src.is_seekable());
300    }
301
302    #[test]
303    fn push_increments_bytes_downloaded() {
304        let buf = StreamBuffer::new(Some(10));
305        buf.push(b"hello");
306        assert_eq!(buf.bytes_downloaded(), 5);
307        buf.push(b"world");
308        assert_eq!(buf.bytes_downloaded(), 10);
309    }
310
311    #[test]
312    fn multiple_readers_independent_positions() {
313        let data = b"0123456789";
314        let buf = filled_buffer(data);
315
316        let mut r1 = buf.reader();
317        let mut r2 = buf.reader();
318
319        r1.seek(SeekFrom::Start(7)).unwrap();
320
321        let mut out1 = [0u8; 3];
322        r1.read_exact(&mut out1).unwrap();
323        assert_eq!(&out1, b"789");
324
325        let mut out2 = [0u8; 3];
326        r2.read_exact(&mut out2).unwrap();
327        assert_eq!(&out2, b"012");
328    }
329
330    #[test]
331    fn test_partial_availability() {
332        // Push only 500 bytes without finish() — simulates in-progress download.
333        let data: Vec<u8> = (0u8..=255).cycle().take(1000).collect();
334        let buf = StreamBuffer::new(Some(1000));
335        buf.push(&data[..500]);
336
337        assert_eq!(buf.bytes_downloaded(), 500);
338        assert_eq!(buf.total_len(), Some(1000));
339
340        // Spawn thread to call finish() after brief delay so reader doesn't block forever.
341        let buf2 = buf.clone();
342        std::thread::spawn(move || {
343            std::thread::sleep(std::time::Duration::from_millis(10));
344            buf2.finish();
345        });
346
347        let mut src = buf.reader();
348        let mut out = Vec::new();
349        src.read_to_end(&mut out).unwrap();
350        assert_eq!(out.len(), 500);
351        assert_eq!(out, &data[..500]);
352    }
353}