web_codecs/video/
encoder.rs

1use std::{cell::RefCell, rc::Rc, time::Duration};
2
3use tokio::sync::{mpsc, watch};
4use wasm_bindgen::prelude::*;
5
6use crate::{EncodedFrame, Error, Timestamp};
7
8use super::{Dimensions, VideoDecoderConfig, VideoFrame};
9
10use derive_more::Display;
11
12#[derive(Debug, Display, Clone, Copy)]
13pub enum VideoBitrateMode {
14	#[display("constant")]
15	Constant,
16
17	#[display("variable")]
18	Variable,
19
20	#[display("quantizer")]
21	Quantizer,
22}
23
24#[derive(Debug, Default, Clone)]
25pub struct VideoEncoderConfig {
26	pub codec: String,
27	pub resolution: Dimensions,
28	pub display: Option<Dimensions>,
29	pub hardware_acceleration: Option<bool>,
30	pub latency_optimized: Option<bool>,
31	pub bitrate: Option<u32>,          // bits per second
32	pub framerate: Option<f64>,        // frames per second
33	pub alpha_preserved: Option<bool>, // keep alpha channel
34	pub scalability_mode: Option<String>,
35	pub bitrate_mode: Option<VideoBitrateMode>,
36
37	// NOTE: This is a custom configuration
38	/// The maximum duration of a Group of Pictures (GOP) before forcing a new keyframe.
39	pub max_gop_duration: Option<Duration>, // seconds
40}
41
42impl VideoEncoderConfig {
43	pub fn new<T: Into<String>>(codec: T, resolution: Dimensions) -> Self {
44		Self {
45			codec: codec.into(),
46			resolution,
47			display: None,
48			hardware_acceleration: None,
49			latency_optimized: None,
50			bitrate: None,
51			framerate: None,
52			alpha_preserved: None,
53			scalability_mode: None,
54			bitrate_mode: None,
55			max_gop_duration: None,
56		}
57	}
58
59	pub async fn is_supported(&self) -> Result<bool, Error> {
60		let res =
61			wasm_bindgen_futures::JsFuture::from(web_sys::VideoEncoder::is_config_supported(&self.into())).await?;
62
63		let supported = js_sys::Reflect::get(&res, &JsValue::from_str("supported"))
64			.unwrap()
65			.as_bool()
66			.unwrap();
67
68		Ok(supported)
69	}
70
71	pub fn is_valid(&self) -> Result<(), Error> {
72		if self.resolution.width == 0 || self.resolution.height == 0 {
73			return Err(Error::InvalidDimensions);
74		}
75
76		if let Some(display) = self.display {
77			if display.width == 0 || display.height == 0 {
78				return Err(Error::InvalidDimensions);
79			}
80		}
81
82		Ok(())
83	}
84
85	pub fn init(self) -> Result<(VideoEncoder, VideoEncoded), Error> {
86		let (frames_tx, frames_rx) = mpsc::unbounded_channel();
87		let (closed_tx, closed_rx) = watch::channel(Ok(()));
88		let config = Rc::new(RefCell::new(None));
89
90		let decoder = VideoEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
91		let decoded = VideoEncoded::new(config, frames_rx, closed_rx);
92
93		Ok((decoder, decoded))
94	}
95}
96
97impl From<&VideoEncoderConfig> for web_sys::VideoEncoderConfig {
98	fn from(this: &VideoEncoderConfig) -> Self {
99		let config = web_sys::VideoEncoderConfig::new(&this.codec, this.resolution.height, this.resolution.width);
100
101		if let Some(Dimensions { width, height }) = this.display {
102			config.set_display_height(height);
103			config.set_display_width(width);
104		}
105
106		if let Some(preferred) = this.hardware_acceleration {
107			config.set_hardware_acceleration(match preferred {
108				true => web_sys::HardwareAcceleration::PreferHardware,
109				false => web_sys::HardwareAcceleration::PreferSoftware,
110			});
111		}
112
113		if let Some(value) = this.latency_optimized {
114			config.set_latency_mode(match value {
115				true => web_sys::LatencyMode::Realtime,
116				false => web_sys::LatencyMode::Quality,
117			});
118		}
119
120		if let Some(value) = this.bitrate {
121			config.set_bitrate(value as f64);
122		}
123
124		if let Some(value) = this.framerate {
125			config.set_framerate(value);
126		}
127
128		if let Some(value) = this.alpha_preserved {
129			config.set_alpha(match value {
130				true => web_sys::AlphaOption::Keep,
131				false => web_sys::AlphaOption::Discard,
132			});
133		}
134
135		if let Some(value) = &this.scalability_mode {
136			config.set_scalability_mode(value);
137		}
138
139		if let Some(_value) = &this.bitrate_mode {
140			// TODO not supported yet
141		}
142
143		config
144	}
145}
146
147#[derive(Debug, Default)]
148pub struct VideoEncodeOptions {
149	// Force or deny a key frame.
150	pub key_frame: Option<bool>,
151	// TODO
152	// pub quantizer: Option<u8>,
153}
154
155pub struct VideoEncoder {
156	inner: web_sys::VideoEncoder,
157	config: VideoEncoderConfig,
158
159	last_keyframe: Rc<RefCell<Option<Timestamp>>>,
160
161	// These are held to avoid dropping them.
162	#[allow(dead_code)]
163	on_error: Closure<dyn FnMut(JsValue)>,
164	#[allow(dead_code)]
165	on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
166}
167
168impl VideoEncoder {
169	fn new(
170		config: VideoEncoderConfig,
171		on_config: Rc<RefCell<Option<VideoDecoderConfig>>>,
172		on_frame: mpsc::UnboundedSender<EncodedFrame>,
173		on_error: watch::Sender<Result<(), Error>>,
174	) -> Result<Self, Error> {
175		let last_keyframe = Rc::new(RefCell::new(None));
176		let last_keyframe2 = last_keyframe.clone();
177
178		let on_error2 = on_error.clone();
179		let on_error = Closure::wrap(Box::new(move |e: JsValue| {
180			on_error.send_replace(Err(Error::from(e))).ok();
181		}) as Box<dyn FnMut(_)>);
182
183		let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
184			// First parameter is the frame, second optional parameter is metadata.
185			let frame: web_sys::EncodedVideoChunk = frame.unchecked_into();
186			let frame = EncodedFrame::from(frame);
187
188			if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
189				// TODO handle metadata
190				if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
191					if !config.is_falsy() {
192						let config: web_sys::VideoDecoderConfig = config.unchecked_into();
193						let config = VideoDecoderConfig::from(config);
194						on_config.borrow_mut().replace(config);
195					}
196				}
197			}
198
199			if frame.keyframe {
200				let mut last_keyframe = last_keyframe2.borrow_mut();
201				if frame.timestamp > last_keyframe.unwrap_or_default() {
202					*last_keyframe = Some(frame.timestamp);
203				}
204			}
205
206			if on_frame.send(frame).is_err() {
207				on_error2.send_replace(Err(Error::Dropped)).ok();
208			}
209		}) as Box<dyn FnMut(_, _)>);
210
211		let init = web_sys::VideoEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
212		let inner: web_sys::VideoEncoder = web_sys::VideoEncoder::new(&init).unwrap();
213		inner.configure(&(&config).into())?;
214
215		Ok(Self {
216			config,
217			inner,
218			last_keyframe,
219			on_error,
220			on_frame,
221		})
222	}
223
224	pub fn encode(&mut self, frame: &VideoFrame, options: VideoEncodeOptions) -> Result<(), Error> {
225		let o = web_sys::VideoEncoderEncodeOptions::new();
226
227		if let Some(key_frame) = options.key_frame {
228			o.set_key_frame(key_frame);
229		} else if let Some(max_gop_duration) = self.config.max_gop_duration {
230			let timestamp = frame.timestamp();
231			let mut last_keyframe = self.last_keyframe.borrow_mut();
232
233			let duration = timestamp - last_keyframe.unwrap_or_default();
234			if duration >= max_gop_duration {
235				o.set_key_frame(true);
236			}
237
238			*last_keyframe = Some(timestamp);
239		}
240
241		self.inner.encode_with_options(frame, &o)?;
242
243		Ok(())
244	}
245
246	pub fn queue_size(&self) -> u32 {
247		self.inner.encode_queue_size()
248	}
249
250	pub fn config(&self) -> &VideoEncoderConfig {
251		&self.config
252	}
253
254	pub async fn flush(&mut self) -> Result<(), Error> {
255		wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
256		Ok(())
257	}
258}
259
260impl Drop for VideoEncoder {
261	fn drop(&mut self) {
262		let _ = self.inner.close();
263	}
264}
265
266pub struct VideoEncoded {
267	config: Rc<RefCell<Option<VideoDecoderConfig>>>,
268	frames: mpsc::UnboundedReceiver<EncodedFrame>,
269	closed: watch::Receiver<Result<(), Error>>,
270}
271
272impl VideoEncoded {
273	fn new(
274		config: Rc<RefCell<Option<VideoDecoderConfig>>>,
275		frames: mpsc::UnboundedReceiver<EncodedFrame>,
276		closed: watch::Receiver<Result<(), Error>>,
277	) -> Self {
278		Self { config, frames, closed }
279	}
280
281	pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
282		tokio::select! {
283			biased;
284			frame = self.frames.recv() => Ok(frame),
285			Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
286		}
287	}
288
289	/// Returns the decoder config, after the first frame has been encoded.
290	pub fn config(&self) -> Option<VideoDecoderConfig> {
291		self.config.borrow().clone()
292	}
293}