use super::Config;
use crate::catalog::hang::CatalogExt;
use crate::container::Frame;
pub struct Import {
track: crate::container::Producer<crate::catalog::hang::Container, hang::catalog::AudioConfig>,
}
impl Import {
pub fn new<E: CatalogExt>(
track: moq_net::track::Producer,
reserved: crate::catalog::Reserved<E>,
config: hang::catalog::AudioConfig,
) -> crate::Result<Self> {
tracing::debug!(name = ?track.name(), ?config, "starting track");
let wire = crate::catalog::hang::Container::try_from(&config)?;
let track = reserved.audio(track, wire, config)?;
Ok(Self { track })
}
pub fn name(&self) -> &str {
self.track.track().name()
}
pub(crate) fn live_edge(&self) -> Option<moq_net::Timestamp> {
self.track.live_edge()
}
pub fn demand(&self) -> moq_net::track::Demand {
self.track.track().demand()
}
pub fn flush(&mut self, timestamp: moq_net::Timestamp, now: std::time::Instant) -> crate::Result<()> {
self.track.flush(timestamp, now)
}
pub fn finish(&mut self) -> crate::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn abort(self, err: moq_net::Error) {
self.track.abort(err);
}
pub fn cut(&mut self, end: Option<moq_net::Timestamp>) -> crate::Result<()> {
self.track.cut(end)?;
Ok(())
}
pub fn discontinuity(&mut self) -> crate::Result<()> {
self.track.discontinuity()?;
Ok(())
}
pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
self.track.seek(sequence)?;
Ok(())
}
pub fn decode<B: moq_net::IntoBytes>(&mut self, frame: B, pts: Option<moq_net::Timestamp>) -> crate::Result<()> {
let timestamp = self.track.timestamp(pts)?;
let keyframe = self.track.needs_keyframe();
self.track.write(Frame {
timestamp,
payload: frame.into_bytes(),
keyframe,
duration: None,
})?;
Ok(())
}
}
pub fn config(init: &[u8]) -> crate::Result<hang::catalog::AudioConfig> {
let mut buf = init;
let mut config: hang::catalog::AudioConfig = Config::parse(&mut buf)?.into();
config.description = Some(bytes::Bytes::copy_from_slice(init));
Ok(config)
}
impl From<Config> for hang::catalog::AudioConfig {
fn from(config: Config) -> Self {
let mut audio = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, config.channel_count);
audio.description = config.encode().ok();
audio.container = hang::catalog::Container::Legacy;
audio
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use moq_net::Timestamp;
#[test]
fn catalog_rate_is_decoder_rate() {
let head = super::Config::new(44_100, 2).encode().unwrap();
let config = super::config(&head).unwrap();
assert_eq!(config.sample_rate, 48_000);
assert_eq!(config.description.as_deref(), Some(head.as_ref()));
let config: hang::catalog::AudioConfig = super::Config::new(44_100, 2).into();
assert_eq!(config.sample_rate, 48_000);
}
#[tokio::test(start_paused = true)]
async fn a_loc_reservation_reaches_the_wire_and_the_catalog() {
let mut broadcast = moq_net::broadcast::Info::new().produce();
let catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap();
let track = broadcast
.create_track("audio", hang::container::track_info(hang::catalog::PRIORITY.audio))
.unwrap();
let subscriber = track.subscribe(None);
let reserved = catalog.reserve();
let config = crate::codec::opus::Config::new(48_000, 2);
let mut config: hang::catalog::AudioConfig = config.into();
config.container = hang::catalog::Container::Loc;
let mut import = super::Import::new(track, reserved, config).unwrap();
let audio = catalog.snapshot().audio.renditions.get("audio").cloned().unwrap();
assert_eq!(audio.container, hang::catalog::Container::Loc);
let payload = b"opus payload";
import
.decode(payload, Some(Timestamp::from_micros(1_000).unwrap()))
.unwrap();
let mut media = crate::container::Consumer::new(
subscriber,
crate::catalog::hang::Container::Loc(crate::container::Kind::Data),
);
let frame = tokio::time::timeout(Duration::from_secs(1), media.read())
.await
.unwrap()
.unwrap()
.unwrap();
assert_eq!(frame.payload.as_ref(), payload);
assert_eq!(frame.timestamp, Timestamp::from_micros(1_000).unwrap());
import.finish().unwrap();
}
}