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