web_codecs/video/
decoder.rs1use bytes::{Bytes, BytesMut};
2use tokio::sync::{mpsc, watch};
3use wasm_bindgen::prelude::*;
4
5use super::{Dimensions, VideoColorSpaceConfig, VideoFrame};
6use crate::{EncodedFrame, Error};
7
8#[derive(Debug, Default, Clone)]
9pub struct VideoDecoderConfig {
10 pub codec: String,
12
13 pub resolution: Option<Dimensions>,
16
17 pub display: Option<Dimensions>,
20
21 pub color_space: Option<VideoColorSpaceConfig>,
23
24 pub description: Option<Bytes>,
29
30 pub hardware_acceleration: Option<bool>,
32
33 pub latency_optimized: Option<bool>,
35}
36
37impl VideoDecoderConfig {
38 pub fn new<T: Into<String>>(codec: T) -> Self {
39 Self {
40 codec: codec.into(),
41 ..Default::default()
42 }
43 }
44
45 pub async fn is_supported(&self) -> Result<bool, Error> {
48 if self.resolution.is_none_or(|d| d.width == 0 || d.height == 0) {
49 return Err(Error::InvalidDimensions);
50 }
51
52 if self.display.is_none_or(|d| d.width == 0 || d.height == 0) {
53 return Err(Error::InvalidDimensions);
54 }
55
56 let res =
57 wasm_bindgen_futures::JsFuture::from(web_sys::VideoDecoder::is_config_supported(&self.into())).await?;
58
59 let supported = js_sys::Reflect::get(&res, &JsValue::from_str("supported"))
60 .unwrap()
61 .as_bool()
62 .unwrap();
63
64 Ok(supported)
65 }
66
67 pub fn build(self) -> Result<(VideoDecoder, VideoDecoded), Error> {
68 let (frames_tx, frames_rx) = mpsc::unbounded_channel();
69 let (closed_tx, closed_rx) = watch::channel(Ok(()));
70 let closed_tx2 = closed_tx.clone();
71
72 let on_error = Closure::wrap(Box::new(move |e: JsValue| {
73 closed_tx.send_replace(Err(Error::from(e))).ok();
74 }) as Box<dyn FnMut(_)>);
75
76 let on_frame = Closure::wrap(Box::new(move |e: JsValue| {
77 let frame: web_sys::VideoFrame = e.unchecked_into();
78 let frame = VideoFrame::from(frame);
79
80 if frames_tx.send(frame).is_err() {
81 closed_tx2.send_replace(Err(Error::Dropped)).ok();
82 }
83 }) as Box<dyn FnMut(_)>);
84
85 let init = web_sys::VideoDecoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
86 let inner: web_sys::VideoDecoder = web_sys::VideoDecoder::new(&init).unwrap();
87 inner.configure(&(&self).into())?;
88
89 let decoder = VideoDecoder {
90 inner,
91 on_error,
92 on_frame,
93 };
94
95 let decoded = VideoDecoded {
96 frames: frames_rx,
97 closed: closed_rx,
98 };
99
100 Ok((decoder, decoded))
101 }
102}
103
104impl From<&VideoDecoderConfig> for web_sys::VideoDecoderConfig {
105 fn from(this: &VideoDecoderConfig) -> Self {
106 let config = web_sys::VideoDecoderConfig::new(&this.codec);
107
108 if let Some(Dimensions { width, height }) = this.resolution {
109 config.set_coded_width(width);
110 config.set_coded_height(height);
111 }
112
113 if let Some(Dimensions { width, height }) = this.display {
114 config.set_display_aspect_width(width);
115 config.set_display_aspect_height(height);
116 }
117
118 if let Some(description) = &this.description {
119 config.set_description(&js_sys::Uint8Array::from(description.as_ref()));
120 }
121
122 if let Some(color_space) = &this.color_space {
123 config.set_color_space(&color_space.into());
124 }
125
126 if let Some(preferred) = this.hardware_acceleration {
127 config.set_hardware_acceleration(match preferred {
128 true => web_sys::HardwareAcceleration::PreferHardware,
129 false => web_sys::HardwareAcceleration::PreferSoftware,
130 });
131 }
132
133 if let Some(value) = this.latency_optimized {
134 config.set_optimize_for_latency(value);
135 }
136
137 config
138 }
139}
140
141impl From<web_sys::VideoDecoderConfig> for VideoDecoderConfig {
142 fn from(this: web_sys::VideoDecoderConfig) -> Self {
143 let resolution = match (this.get_coded_width(), this.get_coded_height()) {
144 (Some(width), Some(height)) if width != 0 && height != 0 => Some(Dimensions { width, height }),
145 _ => None,
146 };
147
148 let display = match (this.get_display_aspect_width(), this.get_display_aspect_height()) {
149 (Some(width), Some(height)) if width != 0 && height != 0 => Some(Dimensions { width, height }),
150 _ => None,
151 };
152
153 let color_space = this.get_color_space().map(VideoColorSpaceConfig::from);
154
155 let description = this.get_description().map(|d| {
156 let buffer = js_sys::Uint8Array::new(&d);
158 let size = buffer.byte_length() as usize;
159
160 let mut payload = BytesMut::with_capacity(size);
161 payload.resize(size, 0);
162 buffer.copy_to(&mut payload);
163
164 payload.freeze()
165 });
166
167 let hardware_acceleration = match this.get_hardware_acceleration() {
168 Some(web_sys::HardwareAcceleration::PreferHardware) => Some(true),
169 Some(web_sys::HardwareAcceleration::PreferSoftware) => Some(false),
170 _ => None,
171 };
172
173 let latency_optimized = this.get_optimize_for_latency();
174
175 Self {
176 codec: this.get_codec(),
177 resolution,
178 display,
179 color_space,
180 description,
181 hardware_acceleration,
182 latency_optimized,
183 }
184 }
185}
186
187pub struct VideoDecoder {
188 inner: web_sys::VideoDecoder,
189
190 #[allow(dead_code)]
192 on_error: Closure<dyn FnMut(JsValue)>,
193 #[allow(dead_code)]
194 on_frame: Closure<dyn FnMut(JsValue)>,
195}
196
197impl VideoDecoder {
198 pub fn decode(&self, frame: EncodedFrame) -> Result<(), Error> {
199 let chunk_type = match frame.keyframe {
200 true => web_sys::EncodedVideoChunkType::Key,
201 false => web_sys::EncodedVideoChunkType::Delta,
202 };
203
204 let chunk = web_sys::EncodedVideoChunkInit::new(
205 &js_sys::Uint8Array::from(frame.payload.as_ref()),
206 frame.timestamp.as_micros() as _,
207 chunk_type,
208 );
209
210 let chunk = web_sys::EncodedVideoChunk::new(&chunk)?;
211 self.inner.decode(&chunk)?;
212
213 Ok(())
214 }
215
216 pub async fn flush(&self) -> Result<(), Error> {
217 wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
218 Ok(())
219 }
220
221 pub fn queue_size(&self) -> u32 {
222 self.inner.decode_queue_size()
223 }
224}
225
226impl Drop for VideoDecoder {
227 fn drop(&mut self) {
228 let _ = self.inner.close();
229 }
230}
231
232pub struct VideoDecoded {
233 frames: mpsc::UnboundedReceiver<VideoFrame>,
234 closed: watch::Receiver<Result<(), Error>>,
235}
236
237impl VideoDecoded {
238 pub async fn next(&mut self) -> Result<Option<VideoFrame>, Error> {
239 tokio::select! {
240 biased;
241 frame = self.frames.recv() => Ok(frame),
242 Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
243 }
244 }
245}