moq-karp 0.3.0

Media over QUIC
Documentation
use serde::{Deserialize, Serialize};

use super::{Audio, Error, Result, Video};

#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct Broadcast {
	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub video: Vec<Video>,

	#[serde(default, skip_serializing_if = "Vec::is_empty")]
	pub audio: Vec<Audio>,
}

impl Broadcast {
	#[allow(clippy::should_implement_trait)]
	pub fn from_str(s: &str) -> Result<Self> {
		Ok(serde_json::from_str(s)?)
	}

	pub fn from_slice(v: &[u8]) -> Result<Self> {
		Ok(serde_json::from_slice(v)?)
	}

	pub fn from_reader(reader: impl std::io::Read) -> Result<Self> {
		Ok(serde_json::from_reader(reader)?)
	}

	pub fn to_string(&self) -> Result<String> {
		Ok(serde_json::to_string(self)?)
	}

	pub fn to_string_pretty(&self) -> Result<String> {
		Ok(serde_json::to_string_pretty(self)?)
	}

	pub fn to_vec(&self) -> Result<Vec<u8>> {
		Ok(serde_json::to_vec(self)?)
	}

	pub fn to_writer(&self, writer: impl std::io::Write) -> Result<()> {
		Ok(serde_json::to_writer(writer, self)?)
	}

	pub async fn fetch(broadcast: moq_transfork::BroadcastConsumer) -> Result<Self> {
		let track = moq_transfork::Track::build("catalog.json")
			.priority(-1)
			.group_order(moq_transfork::GroupOrder::Desc)
			.group_expires(std::time::Duration::ZERO)
			.into();

		let mut track = broadcast.get_track(track).await?;
		let mut group = track.next_group().await?.ok_or(Error::Empty)?;
		let frame = group.read_frame().await?.ok_or(Error::Empty)?;
		let parsed = Self::from_slice(&frame)?;

		Ok(parsed)
	}

	pub fn publish(&self, broadcast: &mut moq_transfork::BroadcastProducer) -> Result<moq_transfork::TrackProducer> {
		let track = moq_transfork::Track::build("catalog.json")
			.priority(-1)
			.group_order(moq_transfork::GroupOrder::Desc)
			.group_expires(std::time::Duration::ZERO)
			.into();

		let mut track = broadcast.insert_track(track);
		self.update(&mut track)?;
		Ok(track)
	}

	pub fn update(&self, track: &mut moq_transfork::TrackProducer) -> Result<()> {
		let mut group = track.append_group();
		let frame = self.to_string()?;
		group.write_frame(frame.into());

		Ok(())
	}
}

#[cfg(test)]
mod test {
	use super::*;
	use crate::catalog;

	#[test]
	fn simple() {
		let mut encoded = r#"{
			"video": [
				{
					"track": {
						"name": "video",
						"priority": 2,
						"group_order": "desc",
						"group_expires": 0.05
					},
					"codec": "avc1.64001f",
					"resolution": {
						"width": 1280,
						"height": 720
					},
					"timescale": 1000,
					"bitrate": 6000000
				}
			],
			"audio": [
				{
					"track": {
						"name": "audio",
						"priority": 1,
						"group_order": "desc",
						"group_expires": 0.05
					},
					"codec": "opus",
					"timescale": 48000,
					"sample_rate": 48000,
					"channel_count": 2,
					"bitrate": 128000
				}
			]
		}"#
		.to_string();

		encoded.retain(|c| !c.is_whitespace());

		let decoded = Broadcast {
			video: vec![Video {
				track: moq_transfork::Track {
					name: "video".to_string(),
					priority: 2,
					group_order: moq_transfork::GroupOrder::Desc,
					group_expires: std::time::Duration::from_millis(50),
				},
				codec: catalog::H264 {
					profile: 0x64,
					constraints: 0x00,
					level: 0x1f,
				}
				.into(),
				description: Default::default(),
				timescale: 1_000,
				resolution: catalog::Dimensions {
					width: 1280,
					height: 720,
				},
				layers: Default::default(),
				bitrate: Some(6_000_000),
			}],
			audio: vec![Audio {
				track: moq_transfork::Track {
					name: "audio".to_string(),
					priority: 1,
					group_order: moq_transfork::GroupOrder::Desc,
					group_expires: std::time::Duration::from_millis(50),
				},
				codec: catalog::AudioCodec::Opus,
				timescale: 48000,
				sample_rate: 48000,
				channel_count: 2,
				bitrate: Some(128_000),
			}],
		};

		let output = Broadcast::from_str(&encoded).expect("failed to decode");
		assert_eq!(decoded, output, "wrong decoded output");

		let output = decoded.to_string().expect("failed to encode");
		assert_eq!(encoded, output, "wrong encoded output");
	}
}