Skip to main content

player/
decoder.rs

1use std::io::{Read, Seek};
2use std::path::Path;
3use symphonia::core::formats::probe::Hint;
4use symphonia::core::io::MediaSource;
5
6struct ReadSeekSource {
7    inner: Box<dyn ReadSeekSendSync>,
8    len: Option<u64>,
9}
10
11trait ReadSeekSendSync: Read + Seek + Send + Sync {}
12impl<T: Read + Seek + Send + Sync> ReadSeekSendSync for T {}
13
14impl ReadSeekSource {
15    fn new(inner: Box<dyn ReadSeekSendSync>, len: Option<u64>) -> Self {
16        Self { inner, len }
17    }
18}
19
20impl Read for ReadSeekSource {
21    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
22        self.inner.read(buf)
23    }
24}
25
26impl Seek for ReadSeekSource {
27    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
28        self.inner.seek(pos)
29    }
30}
31
32impl MediaSource for ReadSeekSource {
33    fn is_seekable(&self) -> bool {
34        true
35    }
36
37    fn byte_len(&self) -> Option<u64> {
38        self.len
39    }
40}
41
42pub fn open_file(path: &Path) -> Result<(Box<dyn MediaSource>, Hint), Box<dyn std::error::Error>> {
43    let file = std::fs::File::open(path)?;
44    let len = file.metadata().ok().map(|m| m.len());
45
46    let mut hint = Hint::new();
47    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
48        hint.with_extension(ext);
49    }
50
51    let source: Box<dyn MediaSource> = Box::new(ReadSeekSource::new(Box::new(file), len));
52    Ok((source, hint))
53}
54
55pub fn from_stream(
56    mut stream: impl Read + Seek + Send + Sync + 'static,
57) -> (Box<dyn MediaSource>, Hint) {
58    let len = stream.seek(std::io::SeekFrom::End(0)).ok();
59    let _ = stream.seek(std::io::SeekFrom::Start(0));
60
61    let source: Box<dyn MediaSource> = Box::new(ReadSeekSource::new(Box::new(stream), len));
62    let hint = Hint::new();
63    (source, hint)
64}
65
66pub fn from_stream_with_len(
67    stream: impl Read + Seek + Send + Sync + 'static,
68    len: Option<u64>,
69) -> (Box<dyn MediaSource>, Hint) {
70    let source: Box<dyn MediaSource> = Box::new(ReadSeekSource::new(Box::new(stream), len));
71    let hint = Hint::new();
72    (source, hint)
73}
74
75/// a read-only source wrapper for non-seekable streams source (e.g. internet radio).
76struct ReadOnlySource {
77    inner: Box<dyn Read + Send + Sync>,
78}
79
80impl Read for ReadOnlySource {
81    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
82        self.inner.read(buf)
83    }
84}
85
86impl Seek for ReadOnlySource {
87    fn seek(&mut self, _pos: std::io::SeekFrom) -> std::io::Result<u64> {
88        Err(std::io::Error::new(
89            std::io::ErrorKind::Unsupported,
90            "seek not supported on radio stream",
91        ))
92    }
93}
94
95impl MediaSource for ReadOnlySource {
96    fn is_seekable(&self) -> bool {
97        false
98    }
99
100    fn byte_len(&self) -> Option<u64> {
101        None
102    }
103}
104
105/// Create a media source from a non-seekable stream with an explicit format hint.
106/// Used for internet radio streams where seeking is not possible.
107pub fn from_stream_with_hint(
108    stream: impl Read + Send + Sync + 'static,
109    extension: &str,
110) -> (Box<dyn MediaSource>, Hint) {
111    let source: Box<dyn MediaSource> = Box::new(ReadOnlySource {
112        inner: Box::new(stream),
113    });
114    let mut hint = Hint::new();
115    hint.with_extension(extension);
116    (source, hint)
117}