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>, 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
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 }
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: 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 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.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 pub fn config(&self) -> Option<VideoDecoderConfig> {
291 self.config.borrow().clone()
292 }
293}