Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions crates/video-streamer/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# video-streamer

This crate takes an unseekable WebM recording (typically from Chrome CaptureStream) and rewrites it into a “fresh” WebM stream that can start playing immediately.
It does this by parsing the incoming WebM, finding the correct cut point, and re-encoding frames.
The output stream begins with a keyframe and valid headers.
This crate takes an unseekable WebM recording and rewrites it into a stream that can start playing immediately.

`webm_stream` still serves one growing file over the original Start/Pull protocol.
`stream_session` accepts a multi-clip recording event stream, reconnects across clips, and emits independent VP8 WebM segments over the same Start/Pull codes.

The input event grammar is:

```text
(ClipStarted Bytes* CaughtUp Bytes* ClipEnded)* SessionEnded
```

Pulls that arrive while a response is pending are queued.
`Stream ended` (type code 3) ends the session.

## Prerequisites

Expand Down
55 changes: 55 additions & 0 deletions crates/video-streamer/src/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use anyhow::Context as _;
use cadeau::xmf::vpx::{VpxCodec, VpxDecoder, VpxImage};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Dimensions {
pub width: u32,
pub height: u32,
}

pub(crate) struct DecodedFrame<'decoder> {
pub image: VpxImage<'decoder>,
pub dimensions: Dimensions,
}

pub(crate) struct InputDecoder {
codec: VpxCodec,
threads: u32,
decoder: Option<VpxDecoder>,
}

impl InputDecoder {
pub(crate) fn new(codec: VpxCodec, threads: u32) -> Self {
Self {
codec,
threads,
decoder: None,
}
}

pub(crate) fn decode<'decoder>(&'decoder mut self, data: &[u8]) -> anyhow::Result<DecodedFrame<'decoder>> {
if self.decoder.is_none() {
self.decoder = Some(
VpxDecoder::builder()
.threads(self.threads)
.width(0)
.height(0)
.codec(self.codec)
.build()?,
);
}

let decoder = self.decoder.as_mut().context("input decoder is missing")?;
decoder.decode(data)?;
let image = decoder.next_frame()?;
let dimensions = Dimensions {
width: image.width(),
height: image.height(),
};
anyhow::ensure!(
dimensions.width > 0 && dimensions.height > 0,
"decoder returned invalid frame dimensions"
);
Ok(DecodedFrame { image, dimensions })
}
}
6 changes: 6 additions & 0 deletions crates/video-streamer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ macro_rules! perf_debug {

pub mod config;
pub mod debug;
mod decoder;
mod normalizer;
mod protocol;
pub mod reopenable;
mod session;
pub(crate) mod streamer;

#[macro_use]
Expand All @@ -39,6 +43,8 @@ pub use streamer::reopenable_file::ReOpenableFile;
pub use streamer::signal_writer::SignalWriter;
#[rustfmt::skip]
pub use streamer::webm_stream;
#[rustfmt::skip]
pub use session::{RecordingEvent, SessionConfig, StartAt, stream_session};

#[cfg(feature = "bench")]
pub mod bench_support;
Loading
Loading