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