1#![forbid(unsafe_code)]
4
5use crate::error::Error;
6use crate::types::Tag;
7
8const TAG_HEADER_LEN: usize = 11;
9const MAX_DATA_SIZE: usize = 0x00FF_FFFF; #[derive(Debug, Clone, Copy, Default)]
15pub struct Muxer {
16 header_written: bool,
17}
18
19impl Muxer {
20 #[must_use]
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn write_header(&mut self, has_audio: bool, has_video: bool, out: &mut Vec<u8>) {
28 out.extend_from_slice(b"FLV");
29 out.push(1); let flags = (u8::from(has_audio) << 2) | u8::from(has_video);
31 out.push(flags);
32 out.extend_from_slice(&9u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); self.header_written = true;
35 }
36
37 #[allow(
39 clippy::cast_possible_truncation,
40 reason = "data_size is bounds-checked against MAX_DATA_SIZE just above; ts>>24 always fits u8"
41 )]
42 pub fn write_tag(&self, tag: &Tag, out: &mut Vec<u8>) -> Result<(), Error> {
43 if !self.header_written {
44 return Err(Error::HeaderNotWritten);
45 }
46 if tag.data.len() > MAX_DATA_SIZE {
47 return Err(Error::TagDataTooLarge(tag.data.len()));
48 }
49 let data_size = tag.data.len();
50 let data_size_bytes = (data_size as u32).to_be_bytes(); out.push(tag.tag_type.value());
53 out.extend_from_slice(&data_size_bytes[1..4]);
54 let ts = tag.timestamp_ms;
55 out.extend_from_slice(&ts.to_be_bytes()[1..4]); out.push((ts >> 24) as u8); out.extend_from_slice(&[0, 0, 0]); out.extend_from_slice(&tag.data);
59
60 let tag_len = u32::try_from(TAG_HEADER_LEN + data_size).unwrap_or(u32::MAX);
61 out.extend_from_slice(&tag_len.to_be_bytes());
62 Ok(())
63 }
64}
65
66#[cfg(test)]
67#[path = "mux_tests.rs"]
68mod tests;