use js_sys::Uint8Array;
use wasm_bindgen::JsCast as _;
use super::{AsyncDecoder, Chunk, Frame, FrameInfo, Result, webcodecs::string_from_js_value};
use crate::{DecodeError, FrameResult, Sender, VideoDataDescription};
pub struct WebImageDecoder {
image_mime_type: String,
output_sender: Sender<FrameResult>,
}
impl WebImageDecoder {
pub fn try_new(
video_descr: &VideoDataDescription,
output_sender: Sender<FrameResult>,
) -> Option<Self> {
Some(Self {
image_mime_type: video_descr.image_codec_mime_type()?.to_owned(),
output_sender,
})
}
}
impl AsyncDecoder for WebImageDecoder {
fn submit_chunk(&mut self, chunk: Chunk) -> Result<()> {
let output_sender = self.output_sender.clone();
let mime_type = self.image_mime_type.clone();
wasm_bindgen_futures::spawn_local(async move {
match decode_image(chunk, &mime_type).await {
Ok(frame) => {
output_sender.send(Ok(frame)).ok();
}
Err(err) => {
output_sender.send(Err(err)).ok();
}
}
});
Ok(())
}
fn reset(&mut self, descr: &VideoDataDescription) -> Result<()> {
if let Some(encoding_details) = &descr.encoding_details {
self.image_mime_type = encoding_details.codec_string.clone();
}
Ok(())
}
}
async fn decode_image(chunk: Chunk, mime_type: &str) -> Result<Frame> {
let uint8_array = Uint8Array::from(chunk.data.as_slice());
let parts = js_sys::Array::new();
parts.push(&uint8_array);
let options = web_sys::BlobPropertyBag::new();
if !mime_type.is_empty() {
options.set_type(mime_type);
}
let blob =
web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &options).map_err(|err| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(format!(
"Failed to create Blob: {}",
string_from_js_value(&err)
)))
})?;
let window = web_sys::window().ok_or_else(|| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(
"No global window object".to_owned(),
))
})?;
let promise = window.create_image_bitmap_with_blob(&blob).map_err(|err| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(format!(
"createImageBitmap failed: {}",
string_from_js_value(&err)
)))
})?;
let bitmap_js = wasm_bindgen_futures::JsFuture::from(promise)
.await
.map_err(|err| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(format!(
"createImageBitmap rejected: {}",
string_from_js_value(&err)
)))
})?;
let bitmap: web_sys::ImageBitmap = bitmap_js.dyn_into().map_err(|_js_err| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(
"createImageBitmap did not return an ImageBitmap".to_owned(),
))
})?;
let init = web_sys::VideoFrameInit::new();
init.set_timestamp(0);
let video_frame = web_sys::VideoFrame::new_with_image_bitmap_and_video_frame_init(
&bitmap, &init,
)
.map_err(|err| {
DecodeError::WebDecoder(super::webcodecs::WebError::Decoding(format!(
"Failed to create VideoFrame from ImageBitmap: {}",
string_from_js_value(&err)
)))
})?;
Ok(Frame {
content: super::webcodecs::WebVideoFrame(video_frame),
info: FrameInfo {
is_sync: Some(true),
sample_idx: Some(chunk.sample_idx),
frame_nr: Some(chunk.frame_nr),
presentation_timestamp: chunk.presentation_timestamp,
duration: chunk.duration,
latest_decode_timestamp: Some(chunk.decode_timestamp),
},
})
}