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 EncoderBitrateMode {
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 bit_rate: Option<f64>, pub frame_rate: Option<f64>, pub alpha_preserved: Option<bool>, pub scalability_mode: Option<String>,
35 pub bitrate_mode: Option<EncoderBitrateMode>,
36
37 pub max_gop_duration: Option<Duration>, }
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 bit_rate: None,
51 frame_rate: 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_tx, config_rx) = watch::channel(None);
89
90 let decoder = VideoEncoder::new(self, config_tx, frames_tx, closed_tx)?;
91 let decoded = VideoEncoded::new(config_rx, 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.bit_rate {
121 config.set_bitrate(value);
122 }
123
124 if let Some(value) = this.frame_rate {
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 }
142
143 config
144 }
145}
146
147#[derive(Debug, Default)]
148pub struct VideoEncodeOptions {
149 pub key_frame: Option<bool>,
151 }
154
155pub struct VideoEncoder {
156 inner: web_sys::VideoEncoder,
157 config: VideoEncoderConfig,
158
159 last_keyframe: Rc<RefCell<Option<Timestamp>>>,
160
161 #[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: watch::Sender<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 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 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.send_replace(Some(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.inner(), &o)?;
242
243 frame.inner().close(); Ok(())
246 }
247
248 pub fn queue_size(&self) -> u32 {
249 self.inner.encode_queue_size()
250 }
251
252 pub fn config(&self) -> &VideoEncoderConfig {
253 &self.config
254 }
255
256 pub async fn flush(&mut self) -> Result<(), Error> {
257 wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
258 Ok(())
259 }
260}
261
262impl Drop for VideoEncoder {
263 fn drop(&mut self) {
264 let _ = self.inner.close();
265 }
266}
267
268pub struct VideoEncoded {
269 config: watch::Receiver<Option<VideoDecoderConfig>>,
270 frames: mpsc::UnboundedReceiver<EncodedFrame>,
271 closed: watch::Receiver<Result<(), Error>>,
272}
273
274impl VideoEncoded {
275 fn new(
276 config: watch::Receiver<Option<VideoDecoderConfig>>,
277 frames: mpsc::UnboundedReceiver<EncodedFrame>,
278 closed: watch::Receiver<Result<(), Error>>,
279 ) -> Self {
280 Self { config, frames, closed }
281 }
282
283 pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
284 tokio::select! {
285 biased;
286 frame = self.frames.recv() => Ok(frame),
287 Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
288 }
289 }
290
291 pub async fn config(&self) -> Option<VideoDecoderConfig> {
292 self.config
293 .clone()
294 .wait_for(|config| config.is_some())
295 .await
296 .ok()?
297 .clone()
298 }
299}