use std::collections::HashMap;
use std::io::{Read, Seek};
use crate::{CodecParameters, Error, Frame, Packet, Result, StreamInfo};
pub trait BytesSource: Read + Seek + Send {}
impl<T: Read + Seek + Send> BytesSource for T {}
pub trait PacketSource: Send {
fn streams(&self) -> &[StreamInfo];
fn next_packet(&mut self) -> Result<Packet>;
fn metadata(&self) -> &[(String, String)] {
&[]
}
fn duration_micros(&self) -> Option<i64> {
None
}
}
pub trait FrameSource: Send {
fn params(&self) -> &CodecParameters;
fn next_frame(&mut self) -> Result<Frame>;
fn metadata(&self) -> &[(String, String)] {
&[]
}
fn duration_micros(&self) -> Option<i64> {
None
}
}
pub enum SourceOutput {
Bytes(Box<dyn BytesSource>),
Packets(Box<dyn PacketSource>),
Frames(Box<dyn FrameSource>),
}
pub type OpenBytesFn = fn(uri: &str) -> Result<Box<dyn BytesSource>>;
pub type OpenPacketsFn = fn(uri: &str) -> Result<Box<dyn PacketSource>>;
pub type OpenFramesFn = fn(uri: &str) -> Result<Box<dyn FrameSource>>;
enum OpenerEntry {
Bytes(OpenBytesFn),
Packets(OpenPacketsFn),
Frames(OpenFramesFn),
}
#[derive(Default)]
pub struct SourceRegistry {
schemes: HashMap<String, OpenerEntry>,
}
impl SourceRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_bytes(&mut self, scheme: &str, opener: OpenBytesFn) {
self.schemes
.insert(scheme.to_ascii_lowercase(), OpenerEntry::Bytes(opener));
}
pub fn register_packets(&mut self, scheme: &str, opener: OpenPacketsFn) {
self.schemes
.insert(scheme.to_ascii_lowercase(), OpenerEntry::Packets(opener));
}
pub fn register_frames(&mut self, scheme: &str, opener: OpenFramesFn) {
self.schemes
.insert(scheme.to_ascii_lowercase(), OpenerEntry::Frames(opener));
}
pub fn open(&self, uri_str: &str) -> Result<SourceOutput> {
let (scheme, _) = split_scheme(uri_str);
let scheme = scheme.to_ascii_lowercase();
if let Some(entry) = self.schemes.get(&scheme) {
return dispatch(entry, uri_str);
}
if let Some(entry) = self.schemes.get("file") {
return dispatch(entry, uri_str);
}
Err(Error::Unsupported(format!(
"no source driver for scheme '{scheme}' (URI: {uri_str})"
)))
}
pub fn schemes(&self) -> impl Iterator<Item = &str> {
self.schemes.keys().map(|s| s.as_str())
}
}
fn dispatch(entry: &OpenerEntry, uri_str: &str) -> Result<SourceOutput> {
match entry {
OpenerEntry::Bytes(open) => open(uri_str).map(SourceOutput::Bytes),
OpenerEntry::Packets(open) => open(uri_str).map(SourceOutput::Packets),
OpenerEntry::Frames(open) => open(uri_str).map(SourceOutput::Frames),
}
}
pub(crate) fn split_scheme(uri: &str) -> (&str, &str) {
if let Some(idx) = uri.find(':') {
let (scheme, rest) = uri.split_at(idx);
let rest = &rest[1..];
if scheme.len() == 1 && scheme.chars().next().unwrap().is_ascii_alphabetic() {
return ("file", uri);
}
let valid = !scheme.is_empty()
&& scheme.chars().next().unwrap().is_ascii_alphabetic()
&& scheme
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'));
if !valid {
return ("file", uri);
}
let rest = rest.strip_prefix("//").unwrap_or(rest);
return (scheme, rest);
}
("file", uri)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::{AudioFrame, Frame};
use crate::packet::Packet;
use crate::stream::{CodecId, CodecParameters, StreamInfo};
use crate::time::TimeBase;
use std::io::{Cursor, Read};
fn open_bytes_mock(_uri: &str) -> Result<Box<dyn BytesSource>> {
Ok(Box::new(Cursor::new(b"hello world".to_vec())))
}
#[test]
fn register_bytes_and_open_returns_bytes_variant() {
let mut reg = SourceRegistry::new();
reg.register_bytes("mockb", open_bytes_mock);
let out = reg.open("mockb://anything").expect("open");
match out {
SourceOutput::Bytes(mut r) => {
let mut buf = String::new();
r.read_to_string(&mut buf).unwrap();
assert_eq!(buf, "hello world");
}
_ => panic!("expected SourceOutput::Bytes"),
}
}
struct MockPacketSource {
streams: Vec<StreamInfo>,
emitted: bool,
}
impl MockPacketSource {
fn new() -> Self {
let params = CodecParameters::audio(CodecId::new("pcm_s16le"));
let s = StreamInfo {
index: 0,
time_base: TimeBase::new(1, 1000),
duration: None,
start_time: None,
params,
};
Self {
streams: vec![s],
emitted: false,
}
}
}
impl PacketSource for MockPacketSource {
fn streams(&self) -> &[StreamInfo] {
&self.streams
}
fn next_packet(&mut self) -> Result<Packet> {
if self.emitted {
return Err(Error::Eof);
}
self.emitted = true;
Ok(Packet::new(0, TimeBase::new(1, 1000), vec![1, 2, 3, 4]))
}
}
fn open_packets_mock(_uri: &str) -> Result<Box<dyn PacketSource>> {
Ok(Box::new(MockPacketSource::new()))
}
#[test]
fn register_packets_and_open_returns_packets_variant() {
let mut reg = SourceRegistry::new();
reg.register_packets("mockp", open_packets_mock);
let out = reg.open("mockp://anything").expect("open");
match out {
SourceOutput::Packets(mut p) => {
assert_eq!(p.streams().len(), 1);
let pkt = p.next_packet().expect("first packet");
assert_eq!(pkt.data, vec![1, 2, 3, 4]);
assert!(matches!(p.next_packet(), Err(Error::Eof)));
}
_ => panic!("expected SourceOutput::Packets"),
}
}
struct MockFrameSource {
params: CodecParameters,
emitted: bool,
}
impl MockFrameSource {
fn new() -> Self {
Self {
params: CodecParameters::audio(CodecId::new("pcm_s16le")),
emitted: false,
}
}
}
impl FrameSource for MockFrameSource {
fn params(&self) -> &CodecParameters {
&self.params
}
fn next_frame(&mut self) -> Result<Frame> {
if self.emitted {
return Err(Error::Eof);
}
self.emitted = true;
Ok(Frame::Audio(AudioFrame {
samples: 1,
pts: Some(0),
data: vec![vec![0u8, 0u8]],
}))
}
}
fn open_frames_mock(_uri: &str) -> Result<Box<dyn FrameSource>> {
Ok(Box::new(MockFrameSource::new()))
}
#[test]
fn register_frames_and_open_returns_frames_variant() {
let mut reg = SourceRegistry::new();
reg.register_frames("mockf", open_frames_mock);
let out = reg.open("mockf://anything").expect("open");
match out {
SourceOutput::Frames(mut f) => {
assert_eq!(f.params().codec_id.as_str(), "pcm_s16le");
let frame = f.next_frame().expect("first frame");
match frame {
Frame::Audio(a) => assert_eq!(a.samples, 1),
_ => panic!("expected audio frame"),
}
assert!(matches!(f.next_frame(), Err(Error::Eof)));
}
_ => panic!("expected SourceOutput::Frames"),
}
}
#[test]
fn unknown_scheme_falls_back_to_file_when_registered() {
let mut reg = SourceRegistry::new();
reg.register_bytes("file", open_bytes_mock);
let out = reg.open("foo://x").expect("fallback open");
assert!(matches!(out, SourceOutput::Bytes(_)));
}
#[test]
fn unknown_scheme_with_no_file_driver_errors() {
let reg = SourceRegistry::new();
let r = reg.open("nope://x");
assert!(matches!(r, Err(Error::Unsupported(_))));
}
#[test]
fn register_overrides_prior_kind() {
let mut reg = SourceRegistry::new();
reg.register_bytes("mock", open_bytes_mock);
reg.register_frames("mock", open_frames_mock);
let out = reg.open("mock://x").expect("open");
assert!(matches!(out, SourceOutput::Frames(_)));
}
#[test]
fn schemes_iterator_lists_registered() {
let mut reg = SourceRegistry::new();
reg.register_bytes("mockb", open_bytes_mock);
reg.register_packets("mockp", open_packets_mock);
reg.register_frames("mockf", open_frames_mock);
let mut names: Vec<&str> = reg.schemes().collect();
names.sort();
assert_eq!(names, vec!["mockb", "mockf", "mockp"]);
}
}