1use std::{cell::RefCell, rc::Rc, time::Duration};
2
3use tokio::sync::{mpsc, watch};
4use wasm_bindgen::{prelude::*, JsCast};
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>, pub framerate: Option<f64>, pub alpha_preserved: Option<bool>, pub scalability_mode: Option<String>,
35 pub bitrate_mode: Option<VideoBitrateMode>,
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 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 let support: web_sys::VideoEncoderSupport = res.unchecked_into();
63 Ok(support.get_supported().unwrap_or(false))
64 }
65
66 pub fn is_valid(&self) -> Result<(), Error> {
67 if self.resolution.width == 0 || self.resolution.height == 0 {
68 return Err(Error::InvalidDimensions);
69 }
70
71 if let Some(display) = self.display {
72 if display.width == 0 || display.height == 0 {
73 return Err(Error::InvalidDimensions);
74 }
75 }
76
77 Ok(())
78 }
79
80 pub fn init(self) -> Result<(VideoEncoder, VideoEncoded), Error> {
81 let (frames_tx, frames_rx) = mpsc::unbounded_channel();
82 let (closed_tx, closed_rx) = watch::channel(Ok(()));
83 let config = Rc::new(RefCell::new(None));
84
85 let decoder = VideoEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
86 let decoded = VideoEncoded::new(config, frames_rx, closed_rx);
87
88 Ok((decoder, decoded))
89 }
90}
91
92impl From<&VideoEncoderConfig> for web_sys::VideoEncoderConfig {
93 fn from(this: &VideoEncoderConfig) -> Self {
94 let config = web_sys::VideoEncoderConfig::new(&this.codec, this.resolution.height, this.resolution.width);
95
96 if let Some(Dimensions { width, height }) = this.display {
97 config.set_display_height(height);
98 config.set_display_width(width);
99 }
100
101 if let Some(preferred) = this.hardware_acceleration {
102 config.set_hardware_acceleration(match preferred {
103 true => web_sys::HardwareAcceleration::PreferHardware,
104 false => web_sys::HardwareAcceleration::PreferSoftware,
105 });
106 }
107
108 if let Some(value) = this.latency_optimized {
109 config.set_latency_mode(match value {
110 true => web_sys::LatencyMode::Realtime,
111 false => web_sys::LatencyMode::Quality,
112 });
113 }
114
115 if let Some(value) = this.bitrate {
116 config.set_bitrate(value);
117 }
118
119 if let Some(value) = this.framerate {
120 config.set_framerate(value);
121 }
122
123 if let Some(value) = this.alpha_preserved {
124 config.set_alpha(match value {
125 true => web_sys::AlphaOption::Keep,
126 false => web_sys::AlphaOption::Discard,
127 });
128 }
129
130 if let Some(value) = &this.scalability_mode {
131 config.set_scalability_mode(value);
132 }
133
134 if let Some(_value) = &this.bitrate_mode {
135 }
137
138 config
139 }
140}
141
142#[derive(Debug, Default)]
143pub struct VideoEncodeOptions {
144 pub key_frame: Option<bool>,
146 }
149
150pub struct VideoEncoder {
151 inner: web_sys::VideoEncoder,
152 config: VideoEncoderConfig,
153
154 last_keyframe: Rc<RefCell<Option<Timestamp>>>,
155
156 #[allow(dead_code)]
158 on_error: Closure<dyn FnMut(JsValue)>,
159 #[allow(dead_code)]
160 on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
161}
162
163impl VideoEncoder {
164 fn new(
165 config: VideoEncoderConfig,
166 on_config: Rc<RefCell<Option<VideoDecoderConfig>>>,
167 on_frame: mpsc::UnboundedSender<EncodedFrame>,
168 on_error: watch::Sender<Result<(), Error>>,
169 ) -> Result<Self, Error> {
170 let last_keyframe = Rc::new(RefCell::new(None));
171 let last_keyframe2 = last_keyframe.clone();
172
173 let on_error2 = on_error.clone();
174 let on_error = Closure::wrap(Box::new(move |e: JsValue| {
175 on_error.send_replace(Err(Error::from(e))).ok();
176 }) as Box<dyn FnMut(_)>);
177
178 let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
179 let frame: web_sys::EncodedVideoChunk = frame.unchecked_into();
181 let frame = EncodedFrame::from(frame);
182
183 if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
184 if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
186 if !config.is_falsy() {
187 let config: web_sys::VideoDecoderConfig = config.unchecked_into();
188 let config = VideoDecoderConfig::from(config);
189 on_config.borrow_mut().replace(config);
190 }
191 }
192 }
193
194 if frame.keyframe {
195 let mut last_keyframe = last_keyframe2.borrow_mut();
196 if frame.timestamp > last_keyframe.unwrap_or_default() {
197 *last_keyframe = Some(frame.timestamp);
198 }
199 }
200
201 if on_frame.send(frame).is_err() {
202 on_error2.send_replace(Err(Error::Dropped)).ok();
203 }
204 }) as Box<dyn FnMut(_, _)>);
205
206 let init = web_sys::VideoEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
207 let inner: web_sys::VideoEncoder = web_sys::VideoEncoder::new(&init).unwrap();
208 inner.configure(&(&config).into())?;
209
210 Ok(Self {
211 config,
212 inner,
213 last_keyframe,
214 on_error,
215 on_frame,
216 })
217 }
218
219 pub fn encode(&mut self, frame: &VideoFrame, options: VideoEncodeOptions) -> Result<(), Error> {
220 let o = web_sys::VideoEncoderEncodeOptions::new();
221
222 if let Some(key_frame) = options.key_frame {
223 o.set_key_frame(key_frame);
224 } else if let Some(max_gop_duration) = self.config.max_gop_duration {
225 let timestamp = frame.timestamp();
226 let mut last_keyframe = self.last_keyframe.borrow_mut();
227
228 let duration = timestamp - last_keyframe.unwrap_or_default();
229 if duration >= max_gop_duration {
230 o.set_key_frame(true);
231 }
232
233 *last_keyframe = Some(timestamp);
234 }
235
236 self.inner.encode_with_options(frame, &o)?;
237
238 Ok(())
239 }
240
241 pub fn queue_size(&self) -> u32 {
242 self.inner.encode_queue_size()
243 }
244
245 pub fn config(&self) -> &VideoEncoderConfig {
246 &self.config
247 }
248
249 pub async fn flush(&mut self) -> Result<(), Error> {
250 wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
251 Ok(())
252 }
253}
254
255impl Drop for VideoEncoder {
256 fn drop(&mut self) {
257 let _ = self.inner.close();
258 }
259}
260
261pub struct VideoEncoded {
262 config: Rc<RefCell<Option<VideoDecoderConfig>>>,
263 frames: mpsc::UnboundedReceiver<EncodedFrame>,
264 closed: watch::Receiver<Result<(), Error>>,
265}
266
267impl VideoEncoded {
268 fn new(
269 config: Rc<RefCell<Option<VideoDecoderConfig>>>,
270 frames: mpsc::UnboundedReceiver<EncodedFrame>,
271 closed: watch::Receiver<Result<(), Error>>,
272 ) -> Self {
273 Self { config, frames, closed }
274 }
275
276 pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
277 tokio::select! {
278 biased;
279 frame = self.frames.recv() => Ok(frame),
280 Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
281 }
282 }
283
284 pub fn config(&self) -> Option<VideoDecoderConfig> {
286 self.config.borrow().clone()
287 }
288}